 OS/2 v1.2 Sample Code


 ACCEL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\ACCEL\ACCEL.C

 /*
  *  ACCEL.C -- Sample demonstrating calls included with INCL_WINACCELERATORS
  *
  *  Overview:
  *        Accelerators are used to reduce the number of keystrokes needed to
  *  execute a command (hence "accelerating" a user's processing time)
  *
  *  Strategy:
  *        This application allows the user to experiment with various setting
  *  by popping up a dialog box in which the user can specify an accelerator.
  *  One possible modification to this program is to have the user hit the
  *  desired key sequence, and to use KbdCharIn() to figure out what the key
  *  sequence is, and then set the accelerator.  Another is to implement the
  *  "Delete" operation, by perhaps listing the accelerators in a list box.
  *  This wasn't done primarily because that would require reorganization
  *  (compression) of the accelerator table:  it could not be easily done with
  *  a WinDeleteAccel call (because such a call does not exist).
  */
 #define INCL_WINACCELERATORS
 #define        INCL_WINBUTTONS                        // Needed for checkboxe
 #define        INCL_WINDIALOGS
 #define        INCL_WINMESSAGEMGR
 #define        INCL_WINFRAMEMGR                // for SC_MINIMIZE constant
 #define INCL_WINWINDOWMGR
 #include <os2.h>

 #include <malloc.h>                        // Needed for dynamic memory alloc
 #include <stdio.h>                        // Needed for sscanf() call
 #include "accel.h"                        // Needed for resource IDs
 /*
  * Globals
  */
 char        ach[8];                         // Temporary:  used to store Key:
 char        szAppName[]        = "ACCEL.EXE";
 char        szClassName[]        = "Accelerator";
 char        szMessage[]        = " - Accelerator Table Example";
 int        cbSize;                         // Size of Accel. Table in bytes
 int        iTemp;                                // Used to store Key: value,
 void        *pTemp;                         // Used so free() won't give warn
 HAB        hab;
 HACCEL        haccSystem;                        // Handle to system accelera
 HACCEL        haccTable;                        // Handle to app-local accelt
 HMQ     hmqAccel;
 HWND        hwndAccel;                        // Client window
 HWND        hwndAccelFrame;                 // Frame window
 PACCELTABLE        pacctTable;                // Points to table with ACCEL e
 /*
     Macros
 */
 #define Message(s) WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, s, \
                         szAppName, 0, MB_OK | MB_ICONEXCLAMATION)
 #define        Check(b)   WinSendDlgItemMsg(hwndDlg, b, \
                         BM_SETCHECK, MPFROMSHORT(1), 0L)
 #define Checked(b) WinSendDlgItemMsg(hwndDlg, b, BM_QUERYCHECK, 0L, 0L)
 /*
     Internals
 */
 BOOL InitializeAccelTable(void);
 /*
  * Main routine...initializes window and message queue
  */
 void cdecl main(void) {
     QMSG qmsg;
     ULONG ctldata;

     /* Initialize a PM application */
     hab = WinInitialize(0);
     hmqAccel = WinCreateMsgQueue(hab, 0);

     /* Register the main window's class */
     if (!WinRegisterClass(hab, szClassName, AccelWndProc, CS_SIZEREDRAW, 0))
         return;
     /*
         Create the window
         We create it without an accelerator table, but we'll load one later
     */
     ctldata = FCF_STANDARD & ~FCF_ACCELTABLE;
     hwndAccelFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, &ctldata,
         szClassName, szMessage, WS_VISIBLE, (HMODULE) NULL, IDR_ACCEL, &hwndA
     WinShowWindow(hwndAccelFrame, TRUE);
     /*
         Load the accelerator tables
     */
     if (!InitializeAccelTable()) {
         Message("Accelerator table not initialized!");
         return;
     }

     /* Poll messages from event queue */
     while(WinGetMsg(hab, (PQMSG)&qmsg, (HWND)NULL, 0, 0))
         WinDispatchMsg(hab, (PQMSG)&qmsg);

     /* Clean up */
     if (!WinDestroyAccelTable(haccTable))
         Message("Could not destroy ACCELTABLE");
     WinDestroyWindow(hwndAccelFrame);
     WinDestroyMsgQueue(hmqAccel);
     WinTerminate(hab);
 }

 MRESULT CALLBACK AccelWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 /*
  * This routine processes WM_PAINT.  It passes
  * everything else to the Default Window Procedure.
  */
     HPS                hPS;
     RECTL        rcl;

     switch (msg) {

         case WM_HELP:
             /* If WM_HELP, pop up Help dialog box */
             WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc, (HMODULE) NULL, IDD_H
             break;

         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {

                 /* On most WM_COMMAND messages, give the About... box */
                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc,
                               (HMODULE) NULL, IDD_ABOUT, NULL);
                     break;

                 /* Create your own accelerator dialog */
                 case IDM_CREATE:
                     WinDlgBox(HWND_DESKTOP, hwnd, CreateDlgProc,
                               (HMODULE) NULL, IDD_CREATE, NULL);

                 default: break;
             }
             break;

         case WM_PAINT:
             /* Open the presentation space */
             hPS = WinBeginPaint(hwnd, NULL, &rcl);

             /* Fill the background with Dark Blue */
             WinFillRect(hPS, &rcl, CLR_DARKBLUE);

             /* Finish painting */
             WinEndPaint(hPS);
             break;

         default: return WinDefWindowProc(hwnd, msg, mp1, mp2); break;
     }
     return 0L;
 }

 MRESULT CALLBACK AboutDlgProc(hwndDlg, msg, mp1, mp2)
 /*
     About... dialog procedure
 */
 HWND hwndDlg;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hwndDlg, TRUE);
                 default: break;
             }
         default: return WinDefDlgProc(hwndDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 MRESULT CALLBACK CreateDlgProc(hwndDlg, msg, mp1, mp2)
 /*
     Create Accelerator dialog procedure
 */
 HWND hwndDlg;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch(msg) {
         case WM_INITDLG:
             /* Set the defaults */
             Check(IDD_CHAR); Check(IDD_CMD);
             break;

         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK:
                     /* Get the accelerator table (allocate an extra space) */
                     cbSize = WinCopyAccelTable(haccTable, NULL, 0);
                     pTemp = (void *) malloc(cbSize + sizeof(ACCEL));
                     pacctTable = (PACCELTABLE) pTemp;
                     cbSize = WinCopyAccelTable(haccTable, pacctTable, cbSize)

 #define accNew        pacctTable->aaccel[pacctTable->cAccel]

                     /*
                         Command:
                             if SYSCOMMAND, make the window minimize.
                             if HELP, we'll pop up a dialog box.
                             otherwise, pop up the About... dialog box.
                     */
                     if (Checked(IDD_SYSCMD)) accNew.cmd = SC_MINIMIZE;
                     else accNew.cmd = IDM_ABOUT;

                     /* Get the states from the dialog box */
                     accNew.fs = 0;
                     if (Checked(IDD_ALT))        accNew.fs |= AF_ALT;
                     if (Checked(IDD_CHAR))        accNew.fs |= AF_CHAR;
                     if (Checked(IDD_CONTROL))        accNew.fs |= AF_CONTROL;
                     if (Checked(IDD_FHELP))        accNew.fs |= AF_HELP;
                     if (Checked(IDD_LONEKEY))        accNew.fs |= AF_LONEKEY;
                     if (Checked(IDD_SCANCODE))        accNew.fs |= AF_SCANCOD
                     if (Checked(IDD_SHIFT))        accNew.fs |= AF_SHIFT;
                     if (Checked(IDD_SYSCMD))        accNew.fs |= AF_SYSCOMMAN
                     if (Checked(IDD_VKEY))        accNew.fs |= AF_VIRTUALKEY;

                     /* Get the key to be defined */
                     WinQueryDlgItemText(hwndDlg, IDD_ENTRY, 8, ach);
                     if (('0' <= ach[0]) && (ach[0] <= '9')) {
                         sscanf(ach, "%i", &iTemp);
                         accNew.key = (USHORT) iTemp;
                     }
                     else accNew.key = (USHORT) ach[0];

                     /* Increment the count of accelerator records */
                     pacctTable->cAccel++;

                     /* Cleanup, then create a new accelerator table */
                     WinDestroyAccelTable(haccTable);
                     haccTable = WinCreateAccelTable(hab, pacctTable);

                     /* Set the new accelerator table, and clean up */
                     WinSetAccelTable(hab, haccTable, hwndAccelFrame);
                     free(pTemp);

                 case DID_CANCEL:
                     WinDismissDlg(hwndDlg, TRUE);

                 default: break;
             }
         default: return WinDefDlgProc(hwndDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 BOOL InitializeAccelTable(void) {
     /*
         Initialize the accelerator table by loading it from the
         resource file.        Note that you can load an accelerator
         table from a DLL, if you change the NULL parameter.
         The system accelerator table is accessible after this
         call:  one possible use for this would be a List...
         dialog box, which would list all system & app. accelerators.
     */
     haccSystem = WinQueryAccelTable(hab, NULL);
     haccTable = WinLoadAccelTable(hab, 0, IDR_ACCEL);
     return WinSetAccelTable(hab, haccTable, hwndAccelFrame);
 }


 APP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\APP.C

 /***************************************************************************\
 * app.c - MDI Sample application
 *
 * Created by Microsoft Corporation, 1989
 *
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINHEAP
 #define INCL_WINSCROLLBARS
 #define INCL_GPIPRIMITIVES

 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"


 /*
     Function prototypes
 */
 BOOL AppInit(VOID);
 BOOL MDIInit(VOID);
 VOID AppTerminate(VOID);
 VOID MDITerminate(VOID);
 BOOL AppNewDocument(USHORT, PSZ);
 VOID TrackSplitbars(HWND, USHORT, SHORT, SHORT);
 VOID MDIDesktopSize(HWND, MPARAM, MPARAM);
 VOID MDIDesktopSetFocus(HWND, MPARAM);
 VOID MDIDesktopActivateDoc(SHORT idMenuitem);
 BOOL AppNewDocument(USHORT, PSZ);
 NPDOC MDINewDocument(USHORT fsStyle, PSZ pszClassName);
 VOID MDISetInitialDocPos(HWND hwndNewFrame);

 VOID AddToWindowMenu(NPDOC);



 int cdecl main(void)
 {
     QMSG qmsg;
     /*
      * Initialize the application globals
      * and create the main window.
      */
     if (AppInit() == FALSE) {
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         return(0);
     }

     /*
      * Initialize the MDI globals etc..
      */
     if (MDIInit() == FALSE) {
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         return(0);
     }

     /*
      * Create an initial, untitled document.
      */
     AppNewDocument(DS_HORZSPLITBAR | DS_VERTSPLITBAR, szDocClass);

     while (WinGetMsg(NULL, (PQMSG)&qmsg, NULL, 0, 0)) {
         WinDispatchMsg(NULL, (PQMSG)&qmsg);
     }

     /*
      * Do the clean-up of the MDI code.
      */
     MDITerminate();

     /*
      * Do the clean-up of the Application.
      */
     AppTerminate();

     DosExit(EXIT_PROCESS, 0);
 }


 MRESULT EXPENTRY MDIWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
     HPS hps;
     RECTL rclPaint, rclWindow;
     POINTL ptlPatternRef;

     switch (msg) {

     case WM_PAINT:
         hps = WinBeginPaint(hwnd, (HPS)NULL, &rclPaint);

         /*
          * Set the pattern to be at the top-left
          * since we're top-left aligning the bits.
          */
         WinQueryWindowRect(hwnd, (PRECTL)&rclWindow);
         ptlPatternRef.x = rclWindow.xLeft;
         ptlPatternRef.y = rclWindow.yTop;
         GpiSetPatternRefPoint(hps, &ptlPatternRef);

         WinFillRect(hps, &rclPaint, SYSCLR_APPWORKSPACE);

         WinEndPaint(hps);
         break;

 #if 0
     case WM_SIZE:

         /* HACK -- only reposition the windows if it is not going to or comin
         from a minimized position, it would be better to what
         WM_WINDOWPOSCHANGED and pay attention to the fs fields of the SWP
         structure */

         if ( SHORT1FROMMP(mp1) && SHORT2FROMMP(mp1) &&
              SHORT1FROMMP(mp2) && SHORT2FROMMP(mp2) ) {
             MDIDesktopSize ( hwnd, mp1, mp2 );
         }
     break;

 #else
     case WM_SIZE:
         MDIDesktopSize ( hwnd, mp1, mp2 );
         break;
 #endif

     case WM_SETFOCUS:
         MDIDesktopSetFocus(hwnd, mp2);
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {

         /*
          * Pass these accelerators onto the active document's
          * frame so it can process it.
          *
          * These are the CMD_ values from the document system
          * menu.
          */
         case CMD_DOCRESTORE:
             WinSendMsg(hwndActiveDoc, WM_SYSCOMMAND, (MPARAM)SC_RESTORE, mp2)
             break;

         case CMD_DOCNEXT:
             WinSendMsg(hwndActiveDoc, WM_SYSCOMMAND, (MPARAM)SC_NEXT, mp2);
             break;

         case CMD_DOCMINIMIZE:
             WinSendMsg(hwndActiveDoc, WM_SYSCOMMAND, (MPARAM)SC_MINIMIZE, mp2
             break;

         case CMD_DOCCLOSE:
             WinSendMsg(hwndActiveDoc, WM_SYSCOMMAND, (MPARAM)SC_CLOSE, mp2);
             break;

         case CMD_DOCSPLIT:
             /*
              * Call TrackSplitbars() with -1 for xMouse to tell
              * it to reposition the pointer to where the
              * splitbars currently are.
              */
             WinSetPointer(HWND_DESKTOP, hptrHVSplit);
             TrackSplitbars(WinWindowFromID(hwndActiveDoc, FID_CLIENT),
                     SPS_VERT | SPS_HORZ, -1, -1);
             WinSetPointer(HWND_DESKTOP, hptrArrow);
             break;

         case CMD_NEW:
             if (AppNewDocument(DS_HORZSPLITBAR | DS_VERTSPLITBAR, szDocClass)
                 WinAlarm(HWND_DESKTOP, WA_ERROR);
             break;

         case CMD_CLOSE:
             /*
              * Close the active document.
              */
             if (hwndActiveDoc)
                 WinSendMsg(hwndActiveDoc, WM_CLOSE, 0L, 0L);
             break;

         case CMD_ABOUT:
             /*
              * Put up the About... dialog box
              */
             WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc, NULL, IDD_ABOUT, NULL
             break;

         case CMD_EXIT:
             WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
             break;

         case CMD_ARRANGETILED:
             ArrangeWindows(AWP_TILED);
             break;

         case CMD_ARRANGECASCADED:
             ArrangeWindows(AWP_CASCADED);
             break;

         default:
             /*
              * The means a window title was selected from
              * the window menu.  Have the MDI code activate
              * the correct window based on the menuitem ID.
              *
              * WARNING: Be sure to keep you applications
              * menuitem IDs < CMD_WINDOWITEMS.
              */

             /* MULTIPLEMENU */
             /*  Also in here we need to pass document unique WM_COMMAND
                 messages on down to the document's client procs */

             if (SHORT1FROMMP(mp1) >= CMD_WINDOWITEMS)
                 MDIDesktopActivateDoc(SHORT1FROMMP(mp1));
             break;
         }
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }

     return (0L);
 }


 BOOL AppNewDocument(USHORT fsStyle, PSZ pszClassName)
 {
     register NPDOC npdocNew;
     HWND hwndFrame, hwndClient;
     HWND hwndHScroll, hwndVScroll;

     npdocNew = MDINewDocument(fsStyle, pszClassName);

     npdocNew->clrBackground = clrNext++;
     if (clrNext > CLR_PALEGRAY)
         clrNext = CLR_BACKGROUND;

     hwndFrame = npdocNew->hwndFrame;
     hwndClient = WinWindowFromID(hwndFrame, FID_CLIENT);

     /*
      * Setup the scrollbars.
      */
     hwndHScroll = WinWindowFromID(hwndFrame, FID_HORZSCROLL);
     WinSendMsg(hwndHScroll, SBM_SETSCROLLBAR, MPFROMSHORT(0),
             MPFROM2SHORT(0, 600));
     hwndHScroll = WinWindowFromID(hwndFrame, ID_HORZSCROLL2);
     WinSendMsg(hwndHScroll, SBM_SETSCROLLBAR, MPFROMSHORT(0),
             MPFROM2SHORT(0, 600));

     hwndVScroll = WinWindowFromID(hwndFrame, FID_VERTSCROLL);
     WinSendMsg(hwndVScroll, SBM_SETSCROLLBAR, MPFROMSHORT(0),
             MPFROM2SHORT(0, 600));
     hwndVScroll = WinWindowFromID(hwndFrame, ID_VERTSCROLL2);
     WinSendMsg(hwndVScroll, SBM_SETSCROLLBAR, MPFROMSHORT(0),
             MPFROM2SHORT(0, 600));

     /*
      * Set the focus the client so the new window will be
      * active when we show it.
      */
     WinSetFocus(HWND_DESKTOP, hwndClient);

     AddToWindowMenu(npdocNew);    /* Moved here from end of
                                   MdiNewDocument routine so that the doc has
                                   been activated, and therefore the main
                                   window has a menu before attempting to add
                                   the doc to the main window's menu */


     /*
      * Set the initial position of the frame window and make it visible.
      */
     MDISetInitialDocPos(hwndFrame);

     return (TRUE);
 }

 MRESULT EXPENTRY AboutDlgProc(HWND hDlg, USHORT msg, MPARAM mp1, MPARAM mp2)
 /*
     About... dialog procedure
 */
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 APPDATA.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\APPDATA.C


 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINACCELERATORS
 #define INCL_WINMENUS
 #define INCL_WINHEAP
 #define INCL_WINPOINTERS

 #include <os2.h>
 #include "app.h"
 #include "mdi.h"

 char szMDIClass[] = "PM MDI Sample App";
 char szDocClass[] = "PM MDI Document";
 USHORT cxBorder, cyBorder, cyHScroll, cxVScroll, cyVScrollArrow;
 USHORT cxScreen, cyScreen, cyIcon, cxByteAlign, cyByteAlign;
 USHORT cxSizeBorder, cySizeBorder;
 ULONG clrNext = CLR_BACKGROUND;
 LONG rglDevCaps[(CAPS_VERTICAL_FONT_RES - CAPS_FAMILY)];

 /* Main globals */
 HAB  hab;
 HHEAP hHeap;
 HMQ  hmqMDI;
 HWND hwndMDI, hwndMDIFrame;
 HWND hwndActiveDoc;
 FONTMETRICS fmSystemFont;
 NPDOC npdocFirst = NULL;

 /* Menu globals */


 HWND hwndSysMenu;


 APPDOC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\APPDOC.C

 /*
     appdoc.c - MDI application
     Created by Microsoft Corporation, 1989
 */
 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINFRAMEMGR
 #define INCL_WINPOINTERS
 #define INCL_WINMENUS
 #define INCL_WINWINDOWMGR
 #define INCL_WINACCELERATORS
 #define INCL_WININPUT
 #define INCL_WINHEAP
 #define INCL_WINSCROLLBARS
 #define INCL_WINRECTANGLES
 #define INCL_WINCOUNTRY
 #define INCL_GPIPRIMITIVES
 #define INCL_GPILOGCOLORTABLE

 #include <os2.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"

 /* Function prototypes */
 VOID AppHScroll(HWND hwnd, MPARAM mp1, MPARAM mp2);
 VOID AppVScroll(HWND hwnd, MPARAM mp1, MPARAM mp2);
 VOID AppEraseBackground(HWND hwnd, HPS hps);
 VOID AppPaint(HWND hwnd);
 VOID MDIClose(HWND hwndClient);
 BOOL MDICreate(HWND);
 BOOL MDIDestroy(HWND);
 BOOL MDIActivate(HWND, BOOL);

 /*
  * The array of RGB values for the rounded
  * rectangles.
  */
 LONG aclrRGB[16] = {
     RGB_RED, RGB_WHITE, RGB_GREEN, RGB_BLACK,
     RGB_BLUE, RGB_WHITE, RGB_YELLOW, RGB_BLACK,
     RGB_CYAN, RGB_BLACK, RGB_PINK, RGB_BLACK,
     RGB_WHITE, RGB_PINK, RGB_BLACK, RGB_RED
 };



 MRESULT EXPENTRY DocWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {

     switch (msg) {

     case WM_COMMAND:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;

     case WM_CREATE:
         if (MDICreate(hwnd) == FALSE)
             return ( (MRESULT) TRUE);
         break;

     case WM_DESTROY:
         MDIDestroy(hwnd);
         break;

     case WM_CLOSE:
         MDIClose(hwnd);
         break;

     case WM_HSCROLL:
         AppHScroll(hwnd, mp1, mp2);
         break;

     case WM_VSCROLL:
         AppVScroll(hwnd, mp1, mp2);
         break;

     case WM_ERASEBACKGROUND:
         AppEraseBackground(hwnd, (HPS)mp1);
         break;

     case WM_PAINT:
         AppPaint(hwnd);
         break;

     case WM_ACTIVATE:
         MDIActivate(hwnd, (BOOL)SHORT1FROMMP(mp1));
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }

     return (0L);
 }


 VOID AppEraseBackground(HWND hwnd, HPS hps)
 {
     RECTL rclPaint;
     HWND hwndFrame, hwndClient;
     register NPDOC npdoc;

     npdoc = NPDOCFROMCLIENT(hwnd);
     hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);

     /*
      * We know the main client is around so
      * go ahead and erase it.
      */
     WinQueryWindowRect(hwnd, &rclPaint);
     WinMapWindowPoints(hwnd, hwndFrame, (PPOINTL)&rclPaint, 2);
     WinFillRect(hps, &rclPaint, npdoc->clrBackground);

     /*
      * Now check to see which of the other client windows
      * are around and erase them.
      *
      * We do all this to avoid erasing the splitbars.
      */
     if (npdoc->fs & DF_SPLITVERT) {

         hwndClient = WinWindowFromID(hwndFrame, ID_CLIENT2);
         /*
          * If it became invisible due to the frame
          * window getting too small, then don't
          * bother drawing.
          */
         if (WinIsWindowVisible(hwndClient) != FALSE) {
             WinQueryWindowRect(hwndClient, &rclPaint);
             WinMapWindowPoints(hwndClient, hwndFrame,
                     (PPOINTL)&rclPaint, 2);
             WinFillRect(hps, &rclPaint, npdoc->clrBackground);
         }
     }

     if (npdoc->fs & DF_SPLITHORZ) {

         hwndClient = WinWindowFromID(hwndFrame, ID_CLIENT3);
         if (WinIsWindowVisible(hwndClient) != FALSE) {
             WinQueryWindowRect(hwndClient, &rclPaint);
             WinMapWindowPoints(hwndClient, hwndFrame,
                     (PPOINTL)&rclPaint, 2);
             WinFillRect(hps, &rclPaint, npdoc->clrBackground);
         }
     }

     /*
      * If we're split in both directions, then there's
      * a ID_CLIENT4 window.
      */
     if ((npdoc->fs & (DF_SPLITHORZ | DF_SPLITVERT)) ==
             (DF_SPLITHORZ | DF_SPLITVERT)) {

         hwndClient = WinWindowFromID(hwndFrame, ID_CLIENT4);
         if (WinIsWindowVisible(hwndClient) != FALSE) {
             WinQueryWindowRect(hwndClient, &rclPaint);
             WinMapWindowPoints(hwndClient, hwndFrame,
                     (PPOINTL)&rclPaint, 2);
             WinFillRect(hps, &rclPaint, npdoc->clrBackground);
         }
     }
 }


 VOID AppHScroll(HWND hwnd, MPARAM mp1, MPARAM mp2)
 {
     HWND hwndFrame;
     NPDOC npdoc;
     RECTL rclPaintBottom, rclPaintTop;
     RECTL rclWindowBottom, rclWindowTop;
     HWND hwndClientBottom, hwndClientTop;
     HWND hwndScrollbar;
     register NPVIEW npviewBottom, npviewTop;
     SHORT posSlider, xOriginOld;
     USHORT cmd, idScrollbar;

     hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
     npdoc = NPDOCFROMCLIENT(hwnd);

     idScrollbar = SHORT1FROMMP(mp1);

     switch (idScrollbar) {

     case FID_HORZSCROLL:
         hwndClientTop = hwnd;
         if (npdoc->fs & DF_SPLITHORZ) {
             hwndClientBottom = WinWindowFromID(hwndFrame, ID_CLIENT3);
         } else {
             hwndClientBottom = NULL;
         }
         break;

     case ID_HORZSCROLL2:
         hwndClientTop = WinWindowFromID(hwndFrame, ID_CLIENT2);
         if (npdoc->fs & DF_SPLITHORZ) {
             hwndClientBottom = WinWindowFromID(hwndFrame, ID_CLIENT4);
         } else {
             hwndClientBottom = NULL;
         }
         break;
     }

     hwndScrollbar = WinWindowFromID(hwndFrame, idScrollbar);

     npviewTop = NPVIEWFROMCLIENT(hwndClientTop);
     WinQueryWindowRect(hwndClientTop, &rclWindowTop);

     if (hwndClientBottom != NULL) {
         npviewBottom = NPVIEWFROMCLIENT(hwndClientBottom);
         WinQueryWindowRect(hwndClientBottom, &rclWindowBottom);
     }

     posSlider = (SHORT) (ULONG) WinSendMsg(hwndScrollbar, SBM_QUERYPOS, NULL,

     cmd = SHORT2FROMMP(mp2);
     switch (cmd) {

     case SB_LINELEFT:
         posSlider -= 16;
         break;

     case SB_LINERIGHT:
         posSlider += 16;
         break;

     case SB_PAGELEFT:
         posSlider -= ((SHORT)rclWindowTop.xRight - 16);
         break;

     case SB_PAGERIGHT:
         posSlider += ((SHORT)rclWindowTop.xRight - 16);
         break;

     case SB_SLIDERPOSITION:
         posSlider = SHORT1FROMMP(mp2);
         break;
     }

     WinSendMsg(hwndScrollbar, SBM_SETPOS, MPFROMSHORT(posSlider), NULL);

     xOriginOld = npviewTop->xOrigin;
     npviewTop->xOrigin = (SHORT) (ULONG) WinSendMsg(hwndScrollbar, SBM_QUERYP
     WinScrollWindow(hwndClientTop, xOriginOld - npviewTop->xOrigin, 0,
             NULL, NULL, NULL, &rclPaintTop, NULL);

     if (hwndClientBottom != NULL) {
         xOriginOld = npviewBottom->xOrigin;
         npviewBottom->xOrigin = npviewTop->xOrigin;
         WinScrollWindow(hwndClientBottom, xOriginOld - npviewBottom->xOrigin,
                 0, NULL, NULL, NULL, &rclPaintBottom, NULL);
     }

     WinMapWindowPoints(hwndClientTop, hwndFrame, (PPOINTL)&rclPaintTop, 2);
     WinInvalidateRect(hwndFrame, &rclPaintTop, TRUE);

     if (hwndClientBottom != NULL) {
         WinMapWindowPoints(hwndClientBottom, hwndFrame, (PPOINTL)&rclPaintBot
         WinInvalidateRect(hwndFrame, &rclPaintBottom, TRUE);
     }
 }


 VOID AppVScroll(HWND hwnd, MPARAM mp1, MPARAM mp2)
 {
     HWND hwndFrame;
     NPDOC npdoc;
     RECTL rclPaintRight, rclPaintLeft;
     RECTL rclWindowRight, rclWindowLeft;
     HWND hwndClientRight, hwndClientLeft;
     HWND hwndScrollbar;
     register NPVIEW npviewRight, npviewLeft;
     SHORT posSlider, yOriginOld;
     USHORT cmd, idScrollbar;

     hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
     npdoc = NPDOCFROMCLIENT(hwnd);

     idScrollbar = SHORT1FROMMP(mp1);

     switch (idScrollbar) {

     case FID_VERTSCROLL:
         hwndClientLeft = hwnd;
         if (npdoc->fs & DF_SPLITVERT) {
             hwndClientRight = WinWindowFromID(hwndFrame, ID_CLIENT2);
         } else {
             hwndClientRight = NULL;
         }
         break;

     case ID_VERTSCROLL2:
         hwndClientLeft = WinWindowFromID(hwndFrame, ID_CLIENT3);
         if (npdoc->fs & DF_SPLITVERT) {
             hwndClientRight = WinWindowFromID(hwndFrame, ID_CLIENT4);
         } else {
             hwndClientRight = NULL;
         }
         break;
     }

     hwndScrollbar = WinWindowFromID(hwndFrame, idScrollbar);

     npviewLeft = NPVIEWFROMCLIENT(hwndClientLeft);
     WinQueryWindowRect(hwndClientLeft, &rclWindowLeft);

     if (hwndClientRight != NULL) {
         npviewRight = NPVIEWFROMCLIENT(hwndClientRight);
         WinQueryWindowRect(hwndClientRight, &rclWindowRight);
     }

     posSlider = (SHORT) (ULONG) WinSendMsg(hwndScrollbar, SBM_QUERYPOS, NULL,

     cmd = SHORT2FROMMP(mp2);
     switch (cmd) {

     case SB_LINEUP:
         posSlider -= 16;
         break;

     case SB_LINEDOWN:
         posSlider += 16;
         break;

     case SB_PAGEUP:
         posSlider -= ((SHORT)rclWindowLeft.yTop - 16);
         break;

     case SB_PAGEDOWN:
         posSlider += ((SHORT)rclWindowLeft.yTop - 16);
         break;

     case SB_SLIDERPOSITION:
         posSlider = SHORT1FROMMP(mp2);
         break;
     }

     WinSendMsg(hwndScrollbar, SBM_SETPOS, MPFROMSHORT(posSlider), NULL);

     yOriginOld = npviewLeft->yOrigin;
     npviewLeft->yOrigin = (SHORT) (ULONG) WinSendMsg(hwndScrollbar, SBM_QUERY
     WinScrollWindow(hwndClientLeft, 0,  npviewLeft->yOrigin - yOriginOld,
             NULL, NULL, NULL, &rclPaintLeft, NULL);

     if (hwndClientRight != NULL) {
         yOriginOld = npviewRight->yOrigin;
         npviewRight->yOrigin = npviewLeft->yOrigin;
         WinScrollWindow(hwndClientRight, 0, npviewRight->yOrigin - yOriginOld
                 NULL, NULL, NULL, &rclPaintRight, NULL);
     }

     WinMapWindowPoints(hwndClientLeft, hwndFrame, (PPOINTL)&rclPaintLeft, 2);
     WinInvalidateRect(hwndFrame, &rclPaintLeft, TRUE);

     if (hwndClientRight != NULL) {
         WinMapWindowPoints(hwndClientRight, hwndFrame, (PPOINTL)&rclPaintRigh
         WinInvalidateRect(hwndFrame, &rclPaintRight, TRUE);
     }
 }


 VOID AppPaint(HWND hwnd)
 {
     HPS hps;
     RECTL rclPaint, rclWindow, rclTest, rclDst;
     POINTL ptl, ptlPatternRef;
     register NPVIEW npview;
     AREABUNDLE abnd;
     LONG clrStart, clrEnd, clrInc, clr;
     SHORT i, j;

     hps = WinBeginPaint(hwnd, (HPS)NULL, &rclPaint);

     /*
      * Go into RGB mode.
      */
     GpiCreateLogColorTable(hps, 0L, LCOLF_RGB, 0L, 0L, NULL);

     /*
      * Make rclPaint an inclusive-inclusive rectangle
      * since that's how GpiBox() will be output.
      */
     rclPaint.xLeft--;
     rclPaint.yBottom--;

     npview = NPVIEWFROMCLIENT(hwnd);

     /*
      * Set the pattern to be at the top-left
      * since we're top-left aligning the bits.
      */
     WinQueryWindowRect(hwnd, (PRECTL)&rclWindow);
     ptlPatternRef.x = rclWindow.xLeft - npview->xOrigin;
     ptlPatternRef.y = rclWindow.yTop + npview->yOrigin;
     GpiSetPatternRefPoint(hps, &ptlPatternRef);

     for (i = 0; i < 8; i++) {

         clr = clrStart = aclrRGB[i * 2];
         clrEnd = aclrRGB[(i * 2) + 1];
         clrInc = (clrEnd - clrStart) / 8;

         for (j = 0; j < 8; j++) {
             abnd.lColor = clr + (j * clrInc);
             GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR, 0L, (PBUNDLE)&abnd);

             /*
              * Only draw the box if it's going to
              * be visible in the update region.
              */
             WinSetRect(NULL, &rclTest, 10 + (i * 75),
                     (SHORT)rclWindow.yTop - 75 - (j * 75), 75 + (i * 75),
                     (SHORT)rclWindow.yTop - 10 - (j * 75));

             WinOffsetRect(NULL, &rclTest, -npview->xOrigin, npview->yOrigin);

             if (WinIntersectRect(NULL, &rclDst, &rclTest, &rclPaint)) {

                 ptl.x = rclTest.xLeft;
                 ptl.y = rclTest.yTop;
                 GpiSetCurrentPosition(hps, (PPOINTL)&ptl);

                 ptl.x = rclTest.xRight;
                 ptl.y = rclTest.yBottom;
                 GpiBox(hps, DRO_OUTLINEFILL, (PPOINTL)&ptl, 40L, 40L);
             }
         }
     }

     WinEndPaint(hps);
 }


 APPINIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\APPINIT.C

 /*
     mdiinit.c - MDI initialization funtions.
     Created by Microsoft Corporation, 1989
 */
 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINHEAP
 #define INCL_GPIBITMAPS
 #define INCL_GPILCIDS
 #define INCL_DEV

 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"


 /* Function prototypes */
 BOOL RegisterWindowClasses(VOID);
 VOID InitSysValues(VOID);


 BOOL AppInit(VOID)
 {
     ULONG ctlData;
     HPS hps;
     HDC hdc;

     hab = WinInitialize(0);

     hmqMDI = WinCreateMsgQueue(hab, 0);

     if (!RegisterWindowClasses())
         return(FALSE);

     ctlData = FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER | FCF_SYSMENU |
               FCF_MENU | FCF_TASKLIST | FCF_SHELLPOSITION | FCF_ICON;

     hwndMDIFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE,
             (VOID FAR *)&ctlData, szMDIClass, (PSZ)NULL,
             WS_VISIBLE | WS_CLIPCHILDREN, NULL, IDR_MDI,
             (HWND FAR *)&hwndMDI);


     if (hwndMDIFrame == NULL)
         return(FALSE);

 /* MULTIPLEMENU */

     /* Remember the first menu so we can put it back when all the documents a
        closed */
     hwndFirstMenu=WinWindowFromID(hwndMDIFrame, FID_MENU);

     hHeap = WinCreateHeap(0, 0, 0, 0, 0, 0);

     if (hHeap == NULL)
         return(FALSE);

     hps = WinGetPS(hwndMDI);

     hdc = GpiQueryDevice(hps);
     DevQueryCaps(hdc, CAPS_FAMILY, CAPS_VERTICAL_FONT_RES, (PLONG)rglDevCaps)

     WinReleasePS(hps);

     InitSysValues();

     return(TRUE);
 }


 VOID InitSysValues(VOID)
 {
     cyTitlebar = (SHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYTITLEBAR);
     cyIcon = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYICON);

     cxBorder = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER);
     cyBorder = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER);

     cxSizeBorder = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXSIZEBORDER);
     cySizeBorder = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYSIZEBORDER);

     cxByteAlign = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXBYTEALIGN);
     cyByteAlign = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYBYTEALIGN);

     cxVScroll = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL);
     cyVScrollArrow = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYVSCROLLARROW
     cyHScroll = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);

     cxScreen = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN);
     cyScreen = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);

     cxMinmaxButton = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXMINMAXBUTTON
 }


 BOOL RegisterWindowClasses(VOID)
 {
     if (!WinRegisterClass(NULL, szMDIClass, (PFNWP)MDIWndProc,
             CS_SYNCPAINT, 0))
         return(FALSE);

     if (!WinRegisterClass(NULL, szDocClass, (PFNWP)DocWndProc,
             0L, sizeof(NPVIEW)))
         return(FALSE);

     return(TRUE);
 }


 VOID AppTerminate(VOID)
 {
     WinDestroyWindow(hwndMDIFrame);

     WinDestroyHeap(hHeap);

     WinDestroyMsgQueue(hmqMDI);

     WinTerminate(hab);
 }


 ARRANGE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\ARRANGE.C

 /***************************************************************************\
 * ARRANGE.c - This file contains code to do window arrangment.
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINHEAP
 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"


 MINMAXFIX  /* add hack to keep the min/max icons in sync with reality */

 /* internal function prototypes */
 BOOL SetTilePositions(PRECTL prc, SHORT cWnd, PSWP aswp);
 SHORT CeilSquareRoot(USHORT us);
 BOOL SetCascadePositions(PRECTL prc, SHORT cWnd, PSWP aswp);
 BOOL SetCascadeParams(PRECTL prc, SHORT *pxEdge, SHORT *pyEdge,
                       SHORT *pxDelta, SHORT *pyDelta, SHORT *cMaxWnd);
 BOOL GetArrangeSwp(USHORT *, SWP *, USHORT *, SWP *);
 BOOL GetArrangeRectangle(PRECTL, BOOL);
 BOOL ArrangeIconPositions(USHORT, PSWP);

 /* internal constants */
 #define CASC_EDGE_NUM       2
 #define CASC_EDGE_DENOM     3

 /* local constants */
 #define ICON_PARK_NUM       5
 #define ICON_PARK_DENOM     3
 #define CLASS_NAME_LENGTH   8

 /***************************************************************************\
 * ArrangeWindowPositions
 *
 * This function sets positions for arranging windows nicely in a rectangle.
 * The hwnd field of each SWP structure should be set by the user, either
 * before or after calling this function.  The function sets all other
 * fields.  The SWP array can then be passed to WinSetMultWindowPos() to do
 * the physical arrangement.  There are two arrangement styles available,
 * AWP_TILED and AWP_CASCADED.
 *
 * AWP_TILED:
 *
 * The tiles are generated by rows, top left (first) to bottom right (last).
 * Each row has the same number of tiles.  The number of tiles in each
 * column will differ by at most one, with each column containing one fewer
 * tile to the left of the other columns.
 *
 * AWP_CASCADED:
 *
 * The windows are generated bottom right (first) to top left (last).
 *
 * Parameters:
 *   prc:    rectangle to contain the tiled windows
 *   cWnd:   number of windows to tile
 *   aswp:   array of SWP structures, one for each tile window
 *   fStyle: the style to arrange the windows
 \***************************************************************************/

 BOOL ArrangeWindowPositions(PRECTL prc, SHORT cWnd, PSWP aswp, USHORT fStyle)
 {
     /* check validity of input rectangle */
     if ((prc->xRight - prc->xLeft < 1) || (prc->yTop - prc->yBottom < 1)) {
         return FALSE;
     }

     /* set window positions */
     switch (fStyle) {
     case AWP_TILED:
         return SetTilePositions(prc, cWnd, aswp);
     case AWP_CASCADED:
         return SetCascadePositions(prc, cWnd, aswp);
     default:
         return FALSE;
     }
 }


 /***************************************************************************\
 * SetTilePositions
 *
 * This function sets positions for tiling windows in a rectangle.
 *
 * NOTE:
 *   There are a few subtleties to this code:
 *
 *   The algorithm lays tiles in a modified NxN grid.  It can be shown
 *   that any positive number of tiles can be laid out in such a grid of
 *   N columns so that each column has at least N-2 tiles and no column
 *   has more than one tile more than any other.  Proof left to the
 *   interested reader.
 *
 *   The tiles coordinates are not generated by stepping over a fixed
 *   interval since this will not usually fill the rectangle completely.
 *   Thus the offset at each step is calculated from the previous tile
 *   to the correct fractional position within the whole rectangle.
 *
 *   Since the last "row" of tiles may not have any members in the beginning
 *   columns, these tiles are addressed differently in the SWP array to
 *   account for the "missing" tiles.
 *
 * Parameters:
 *   prc:        rectangle to contain the tiled windows
 *   cWnd:        number of windows to tile the rectangle with
 *   aswp:        array of SWP structures, one for each tile window
 \***************************************************************************/

 BOOL SetTilePositions(PRECTL prc, SHORT cWnd, PSWP aswp)
 {
     register SHORT usRoot;
     register SHORT cExtras;
     SHORT iChange;
     SHORT cDiff;
     SHORT x, y, cx, cy;
     SHORT iRow, iCol;

     /* get grid dimensions */
     usRoot = CeilSquareRoot(cWnd);
     cExtras = usRoot * usRoot - cWnd;

     /* find column where number of rows increases and find initial
        difference of rows versus columns */
     if (cExtras >= usRoot) {
         iChange = cExtras - usRoot;
         cDiff = 2;
     } else {
         iChange = cExtras;
         cDiff = 1;
     }

     /* assign x coordinates */
     x = (SHORT)prc->xLeft;
     cx = 0;
     for (iCol = 0; iCol < usRoot; iCol++) {
         x += cx - cxBorder;
         cx = ((SHORT)prc->xLeft) +
              (((SHORT)(prc->xRight - prc->xLeft)) * (iCol + 1)) / usRoot -
              x + cxBorder;
         for (iRow = 0; iRow < usRoot - cDiff; iRow++) {
             aswp[iRow * usRoot + iCol].x = x;
             aswp[iRow * usRoot + iCol].cx = cx;
             aswp[iRow * usRoot + iCol].fs = SWP_SIZE | SWP_MOVE;
         }
         /* assign "extra" row */
         if (iCol >= iChange) {
             aswp[iRow * usRoot + iCol - iChange].x = x;
             aswp[iRow * usRoot + iCol - iChange].cx = cx;
             aswp[iRow * usRoot + iCol - iChange].fs = SWP_SIZE | SWP_MOVE;
         }
     }

     /* assign y coordinates, columns without extra row */
     y = (SHORT)prc->yBottom;
     cy = 0;
     for (iRow = usRoot - cDiff - 1; iRow >= 0; iRow--) {
         y += cy - cyBorder;
         cy = ((SHORT)prc->yBottom) +
              (((SHORT)(prc->yTop - prc->yBottom)) * (usRoot - cDiff - iRow))
                 (usRoot - cDiff) - y + cyBorder;
         for (iCol = 0; iCol < iChange; iCol++) {
             aswp[iRow * usRoot + iCol].y = y;
             aswp[iRow * usRoot + iCol].cy = cy;
         }
     }

     /* assign y coordinates, columns with extra row */
     /* do last row first (different offsets) */
     y = (SHORT)prc->yBottom - cyBorder;
     cy = ((SHORT)(prc->yTop - prc->yBottom)) / (usRoot - cDiff + 1) +
          2 * cyBorder;
     for (iCol = iChange; iCol < usRoot; iCol++) {
         aswp[usRoot * (usRoot - cDiff) + iCol - iChange].y = y;
         aswp[usRoot * (usRoot - cDiff) + iCol - iChange].cy = cy;
     }
     for (iRow = usRoot - cDiff - 1; iRow >= 0; iRow--) {
         y += cy - cyBorder;
         cy = ((SHORT)(prc->yBottom)) +
                 (((SHORT)(prc->yTop - prc->yBottom)) * (usRoot - cDiff - iRow
                 / (usRoot - cDiff + 1) - y + cyBorder;
         for (iCol = iChange; iCol < usRoot; iCol++) {
             aswp[iRow * usRoot + iCol].y = y;
             aswp[iRow * usRoot + iCol].cy = cy;
         }
     }

     return TRUE;
 }


 /***************************************************************************\
 * CeilSquareRoot
 *
 * This function returns the smallest integer greater or equal to the square
 * root of an unsigned 16 bit integer.
 *
 * Parameter:
 *   us: value to take the root of
 \***************************************************************************/

 SHORT CeilSquareRoot(register USHORT us)
 {
     register SHORT i;

     /* prevent overflow of large numbers */
     if (us > 0xFE * 0xFE)
         return 0xFF;

     /* iterate up past root */
     for (i = 0; i*i < (SHORT) us; i++)
         ;
     return i;
 }


 /***************************************************************************\
 * SetCascadePositions
 *
 * This function sets positions for cascading windows in a rectangle.
 *
 * Parameters:
 *   prc:        rectangle to contain the cascaded windows
 *   cWnd:        number of windows to cascade
 *   aswp:        array of SWP structures, one for each cascaded window
 \***************************************************************************/

 BOOL SetCascadePositions(PRECTL prc, SHORT cWnd, PSWP aswp)
 {
     SHORT xEdge, yEdge;
     SHORT xDelta, yDelta;
     SHORT cMaxWnd;
     register SHORT x, y;
     SHORT i, j;
     RECTL rc;

     /* set cascade parameters */
     rc.xLeft = prc->xLeft - cxBorder;
     rc.xRight = prc->xRight + cyBorder;
     rc.yBottom = prc->yBottom - cyBorder;
     rc.yTop = prc->yTop + cyBorder;
     if (!SetCascadeParams((PRECTL)&rc, &xEdge, &yEdge, &xDelta, &yDelta,
                           &cMaxWnd)) {
         return FALSE;
     }

     if (cWnd <= cMaxWnd) {
         /* only one run needed; move to top left corner */
         x = (SHORT)rc. xLeft;
         y = (SHORT)rc. yTop - yEdge;
         for (i = cWnd - 1; i >= 0; i--) {
             aswp[i].x = x;
             aswp[i].y = y;
             aswp[i].cx = xEdge;
             aswp[i].cy = yEdge;
             aswp[i].fs = SWP_SIZE | SWP_MOVE;
             x += xDelta;
             y -= yDelta;
         }

     } else {

         /* multiple runs necessary; start at bottom right, iterate up to
            top left */

         i = 0;

         while (i < cWnd) {

             /* even run */
             x = ((SHORT)rc. xLeft) + (cMaxWnd-1) * xDelta;
             y = ((SHORT)rc. yTop) - yEdge - (cMaxWnd-1) * yDelta;
             for (j = 0; j < cMaxWnd; j++) {
                 aswp[i].x = x;
                 aswp[i].y = y;
                 aswp[i].cx = xEdge;
                 aswp[i].cy = yEdge;
                 aswp[i].fs = SWP_SIZE | SWP_MOVE;
                 x -= xDelta;
                 y += yDelta;
                 if (++i >= cWnd)
                     break;
             }

             if (i >= cWnd)
                 break;

             /* odd run, offset by half delta y, one and one half delta x */
             x = ((SHORT)rc. xLeft) + (cMaxWnd-1) * xDelta + xDelta/2;
             y = ((SHORT)rc. yTop) - yEdge - (cMaxWnd-1) * yDelta + yDelta/2;
             for (j = 0; j < cMaxWnd - 1; j++) {
                 aswp[i].x = x;
                 aswp[i].y = y;
                 aswp[i].cx = xEdge;
                 aswp[i].cy = yEdge;
                 aswp[i].fs = SWP_SIZE | SWP_MOVE;
                 x -= xDelta;
                 y += yDelta;
                 if (++i >= cWnd)
                     break;
             }
         }
     }

     return TRUE;
 }


 /***************************************************************************\
 * SetCascadeParams
 *
 * This function sets parameters for cascading windows.        The window edge
 * are based on a fraction CASC_EDGE_NUM/CASC_EDGE_DENOM of the rectangle.
 * The x delta is four system font characters across, the y delta is two
 * system lines high.
 *
 * Parameters:
 *   prc:        rectangle to contain the windows
 *   pxEdge:        width of the cascaded windows
 *   pyEdge:        height of the cascaded windows
 *   pxDelta:        x cascade offset
 *   pyDelta:        y cascade offset
 *   pcMaxWnd:        maximum number of windows in a cascade
 \***************************************************************************/

 BOOL SetCascadeParams(PRECTL prc, SHORT *pxEdge, SHORT *pyEdge, SHORT *pxDelt
         SHORT *pyDelta, SHORT *pcMaxWnd)
 {
     register SHORT xEdge, yEdge;
     SHORT xDelta, yDelta;
     SHORT cMaxWnd;

     /* get x and y deltas from system values */
     xDelta = LOUSHORT(WinQuerySysValue(HWND_DESKTOP, SV_CXSIZEBORDER)) +
              LOUSHORT(WinQuerySysValue(HWND_DESKTOP, SV_CXMINMAXBUTTON)) / 2
     yDelta = LOUSHORT(WinQuerySysValue(HWND_DESKTOP, SV_CYSIZEBORDER)) +
              LOUSHORT(WinQuerySysValue(HWND_DESKTOP, SV_CYTITLEBAR))
              - cyBorder;

     /* get initial cut at yEdge using fraction */
     yEdge = (((SHORT)(prc->yTop - prc->yBottom)) * CASC_EDGE_NUM) /
             CASC_EDGE_DENOM;

     /* determine maximum number of deltas used per run */
     cMaxWnd = (((SHORT)(prc->yTop - prc->yBottom)) - yEdge) / yDelta;

     /* set x and y edges so full cascade will fill rectangle completely */
     xEdge = ((SHORT)(prc->xRight - prc->xLeft)) - xDelta/2 - cMaxWnd * xDelta
     yEdge = ((SHORT)(prc->yTop - prc->yBottom)) - cMaxWnd * yDelta;

     /* check that values are reasonable */
     if (cMaxWnd < 1 || xEdge < 1 || yEdge < 1) {
         return FALSE;
     }

     *pxEdge = xEdge;
     *pyEdge = yEdge;
     *pxDelta = xDelta;
     *pyDelta = yDelta;
     /* return cMaxWnd as the maximum number of windows in a cascade */
     *pcMaxWnd = cMaxWnd + 1;

     return TRUE;
 }


 /***************************************************************************\
 * ArrangeWindows
 *
 * This function arranges application document windows.
 *
 * Returns:
 *   TRUE if successful
 *   FALSE otherwise
 \***************************************************************************/

 BOOL ArrangeWindows(USHORT fStyle)
 {
     USHORT cswpWnd, cswpIcon;
     RECTL rcl;
     register BOOL fReturn = FALSE;
     SWP NEAR *npswpWnd;
     SWP NEAR *npswpIcon;

     npswpWnd = (SWP NEAR *) WinAllocMem(hHeap, sizeof(SWP) * cDocs);
     npswpIcon = (SWP NEAR *) WinAllocMem(hHeap, sizeof(SWP) * cDocs);

     GetArrangeSwp(&cswpWnd, npswpWnd, &cswpIcon, npswpIcon);

     GetArrangeRectangle((PRECTL)&rcl, (BOOL)cswpIcon);

     /* set window positions */
     if (!ArrangeWindowPositions((PRECTL)&rcl, cswpWnd, (PSWP)npswpWnd, fStyle
         !ArrangeIconPositions(cswpIcon, (PSWP)npswpIcon)) {
         goto ARRANGE_CLEANUP;
     }

 #if 1
     /* rearrange the windows */
     WinSetMultWindowPos(NULL, (PSWP)npswpWnd, cswpWnd);
     WinSetMultWindowPos(NULL, (PSWP)npswpIcon, cswpIcon);
 #endif
     fReturn = TRUE;

 ARRANGE_CLEANUP:
     WinFreeMem(hHeap, (NPBYTE)npswpWnd, sizeof(SWP) * cDocs);
     WinFreeMem(hHeap, (NPBYTE)npswpIcon, sizeof(SWP) * cDocs);

     return fReturn;
 }

 /***************************************************************************\
 * GetArrangeHandles
 *
 * This function generates the handles of all windows to be arranged and
 * creates an array of SWP structures containing those handles.  Minimized
 * and non-minimized windows are separated.  Non-frame, invisible and
 * non-sizeable windows are ignored.
 *
 * Parameter:
 *   npcswpWnd:        number of nonminimized windows found
 *   npswpWnd:         array of SWP structures for nonminimized windows
 *   npcswpIcon:       number of minimized windows found
 *   npswpIcon:        array of SWP structures for minimized windows
 *
 * Returns:
 *   TRUE if successful
 *   FALSE otherwise
 \***************************************************************************/

 BOOL GetArrangeSwp(USHORT *npcswpWnd, SWP *npswpWnd, USHORT *npcswpIcon,
         SWP *npswpIcon)
 {
     register USHORT cWnd, cIcon;
     ULONG ulStyle;
     HWND hwnd;
     register NPDOC npdoc;

     cWnd = 0;
     cIcon = 0;

     /* enumerate windows and selectively add them to the arrange lists */
     for (hwnd = WinQueryWindow(hwndMDI, QW_TOP, FALSE);
          hwnd;
          hwnd = WinQueryWindow(hwnd, QW_NEXT, FALSE)) {

         /* make sure the window is visible and owned by the app client window
         ulStyle = WinQueryWindowULong(hwnd, QWL_STYLE);
         if (WinQueryWindow(hwnd, QW_OWNER, FALSE) ||
             !(ulStyle & WS_VISIBLE)) {
             continue;
         }

         if (ulStyle & WS_MINIMIZED) {
             npswpIcon->hwnd = hwnd;
             npswpIcon++;
             cIcon++;
         } else {
             /* restore maximized windows */
             if (ulStyle & WS_MAXIMIZED) {

 #ifdef MINMAXFIX
                 /* Bring the min/max buttons back to life for a moment so
                    they stay in sync when the window is restored.  Then put
                    them back to the object window 07-Sep-1989 johnba
                 */

                 npdoc = NPDOCFROMCLIENT(WinWindowFromID(hwnd,FID_CLIENT));
                 WinSetParent(npdoc->hwndMinmax, hwnd, FALSE);
 #endif
                 WinSetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_RESTORE );
 #ifdef MINMAXFIX


                 if (hwndActiveDoc != hwnd) {
                     WinSetParent(npdoc->hwndMinmax, HWND_OBJECT, FALSE);
                     WinSendMsg(hwnd, WM_UPDATEFRAME, 0L, 0L);
                     }
 #endif
                 }
             npswpWnd->hwnd = hwnd;
             npswpWnd++;
             cWnd++;
         }
     }

     *npcswpWnd = cWnd;
     *npcswpIcon = cIcon;
     return TRUE;
 }


 /***************************************************************************\
 * GetArrangeRectangle
 *
 * This function determines the area in which task windows are arranged.
 *
 * Parameter:
 *   prc:        the generated area rectangle
 *   fIconPark:        specifies if room should be made for icon parking lot
 *
 * Returns:
 *   TRUE if successful
 *   FALSE otherwise
 \***************************************************************************/

 BOOL GetArrangeRectangle(PRECTL prc, BOOL fIconPark)
 {
     register USHORT yIcon;
     register SHORT cxBorderInset;

     /* get dimensions of desktop window */
     WinQueryWindowRect(hwndMDI, prc);

     cxBorderInset = (SHORT)(WinQuerySysValue(HWND_DESKTOP, SV_CXBYTEALIGN) -
                        WinQuerySysValue(HWND_DESKTOP, SV_CXSIZEBORDER));
     WinInflateRect(NULL, prc, -cxBorderInset, -cxBorderInset *
             (cyBorder / cxBorder));

     if (fIconPark) {
         /* make room for single row of icon carpark */
         yIcon = LOUSHORT(WinQuerySysValue(HWND_DESKTOP, SV_CYICON));
         prc->yBottom += (yIcon * ICON_PARK_NUM) / ICON_PARK_DENOM;
     }

     return TRUE;
 }

 /***************************************************************************\
 * ArrangeIconPositions
 *
 * This function sets positions for minimized windows.
 *
 * Parameters:
 *   cIcon:        number of icons to position
 *   aswp:        array of SetWindowPos structures for those icons
 *
 * Returns:
 *   TRUE if successful
 *   FALSE otherwise
 \***************************************************************************/

 BOOL ArrangeIconPositions(USHORT cIcon, PSWP aswpIcon)
 {
     register USHORT i;

     for (i = 0; i < cIcon; i++) {
         aswpIcon[i].x = 0;
         aswpIcon[i].y = 0;
         aswpIcon[i].fs = SWP_MOVE;
     }

     return TRUE;
 }


 AVIO.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BROWSE\AVBROWSE\AVIO.C

 /*
     avio.c -- AVIO action routines

     Implements scrollbars, sets up an AVIO Presentation Space
 */
 #define  INCL_AVIO
 #define         INCL_DEV
 #define  INCL_VIO
 #define  INCL_WINWINDOWMGR
 #define  INCL_WINSYS
 #define  INCL_WINMESSAGEMGR
 #define  INCL_WINTRACKRECT
 #define  INCL_WINFRAMEMGR
 #define  INCL_WINSCROLLBARS
 #include <os2.h>
 <string.h>        /* One strlen() call in the Blast() macro */
 "avio.h"        /* Get Avio-prefixed routine prototypes   */
 /*
     Constants
 */
 char        Blank[2] = { 0x20, 0x07 };        /* <Space> + EGA white attribut
 /*
     Macros to make the code more readable
 */
 /* Upper and Lower Bound Calculations */
 #define        Abs(a)                (((a) > 0) ? (a) : (-(a)))
 #define        Min(a,b)        (((a) < (b)) ? (a) : (b))
 #define        Max(a,b)        (((a) > (b)) ? (a) : (b))
 #define LowerBound(pos, disp, lbound) Max(pos - disp, lbound)
 #define UpperBound(pos, disp, ubound) Min(pos + disp, ubound)

 /* Scroll Bar Abbreviations */
 #define DisableSB(hSB)        WinSetParent(hSB, HWND_OBJECT, TRUE)
 #define EnableSB(hSB)         WinSetParent(hSB, hWndFrame,   TRUE)
 #define SetScroll(h, pos, max) \
     WinSendMsg(h, SBM_SETSCROLLBAR, MPFROM2SHORT(pos, 0), MPFROM2SHORT(0, max

 /* Scrollbar redraw macros */
 #define UpdateOn(c, hsb)        if (!(++c)) WinEnableWindowUpdate(hsb, TRUE)
 #define UpdateOff(c, hsb)        if (!(c--)) WinEnableWindowUpdate(hsb, FALSE
 #define        UpdateFrame(sb)        \
         WinSendMsg(hWndFrame, WM_UPDATEFRAME, MPFROMLONG(sb), 0L)

 /* Scrolling Macros */
 ClearScreen()        ScrollUp(-1)        /* Scroll up an "infinite" # lines *
 #define ScrollDown(n)        VioScrollDn(0, 0, -1, -1, n, Blank, hVPS)
 #define ScrollUp(n)        VioScrollUp(0, 0, -1, -1, n, Blank, hVPS)

 /* RectL -> SWP conversion macros */
 #define        lcx(r)                ((r.xRight - r.xLeft) + 1)
 #define        lcy(r)                ((r.yTop - r.yBottom) + 1)

 /* Miscellaneous macros */
 #define Blast(l, x, y)        VioWrtCharStr(l, Min((SHORT) strlen(l), cxChScr
 #define CalcChars(sPg, sCh) \
     ((sCh) ? (Max(((sPg) / (sCh)), 0)) : 0)
 #define        SetCellSize(h,w) VioSetDeviceCellSize(h, w, hVPS)
 #define        Value(value)        WinQuerySysValue(HWND_DESKTOP, value)
 /*
     File-Local Variables
 */
 HDC        hDC;                /* Device Context */
 HVPS        hVPS;                /* Virtual PS */
 int        iTopLine;        /* PS Line of window corner */
 int        iCurCol;         /* Current column of window corner */
 int        cxChPage;        /* Width and height of our window, in characters
 int        cyChPage;
 int        iMaxHorz;        /* Scroll bar upper bounds */
 int        iMaxVert;
 BOOL        fNeedHorz;        /* Do we need the scroll bars or not? */
 BOOL        fNeedVert;
 HWND        hWndHorzSB;        /* Window handles of ScrollBar windows */
 HWND        hWndVertSB;
 extern        HWND        hWndFrame;        /* Client, frame windows */
 extern        HWND        hWndClient;
 PFNWP        pfnOldClient;        /* Old Client Window Procedure pointer */
 PFNWP        pfnOldFrame;        /* Old Frame  Window Procedure pointer */
 SHORT        cyChPS;                /* Number of rows in AVIO PS */
 SHORT        cxChPS;                /* Number of cols in AVIO PS */
 SHORT        cyChScreen;                /* Number of rows in display space */
 SHORT        cxChScreen;                /* Number of cols in display space */
 PFNQL        pfnQueryLine;
 /*
     Measurements used to help make the window look nice
 */
 LONG        cxConstant, cyConstant;                        /* Miscellaneous f
 int        cxMaxFrame, cyMaxFrame;                        /* Maximum frame wi
 LONG        lChWidth,   lChHeight;
 SHORT        cxMaxClient, cyMaxClient;                /* Client area bounds
 BOOL        fCreatedPS;                                /* AVIO PS created */
 int        cHUpdate = -1;                                /* Keep track of upd
 int        cVUpdate = -1;
 /*
    Local prototypes
 */
 void FixScroll(BOOL, BOOL, HWND, ULONG, int *, int, int *);
 void UpdateScrollBars(RECTL);
 void Refresh(void);
 void Update(USHORT, USHORT, USHORT);
 /*
     The actual routines
 */
 void AvioInit(PLBINFO plbi) {
 /*
     Initialize Presentation Space, Device Context, Scroll Bars
 */
     VIOCURSORINFO vci;
     /*
         Initialize the line buffer info
     */
     cyChScreen        = plbi->sRows;
     cxChScreen        = plbi->sCols;
     cyChPS        = plbi->sPSrows;
     cxChPS        = plbi->sPScols;
     pfnQueryLine = plbi->pfnQL;
     /*
         One Time Initializations...
     */
     if (!fCreatedPS) {
         /*
            Create the AVIO Presentation Space, with one attribute byte
         */
         hDC = WinOpenWindowDC(hWndClient);        /* Open the device context
         VioCreatePS(&hVPS, cyChPS, cxChPS + 1, 0, 1, 0);
         VioAssociate(hDC, hVPS);                /* Link the PS with the DC */
         /*
             Turn off the cursor (set invisible attribute)
         */
         VioGetCurType(&vci, hVPS);
         vci.attr = -1;
         VioSetCurType(&vci, hVPS);
         /*
             Measure the frame components
         */
         cxConstant = 0;
         cyConstant = Value(SV_CYTITLEBAR) + Value(SV_CYMENU);
         /*
             Snag scroll bar info
         */
         hWndHorzSB        = WinWindowFromID(hWndFrame,  FID_HORZSCROLL);
         hWndVertSB        = WinWindowFromID(hWndFrame,  FID_VERTSCROLL);
         fNeedHorz        = fNeedVert        = TRUE;
         /*
             Setup the Client and Frame routines
         */
         pfnOldFrame        = WinSubclassWindow(hWndFrame,  AvioFrameWndProc);
         pfnOldClient        = WinSubclassWindow(hWndClient, AvioClientWndProc
         fCreatedPS        = TRUE;
     }
     /*
         Repaint the screen
     */
     iTopLine = iCurCol = 0;
     AvioStartup(plbi->fLargeFont);
 }

 void AvioStartup(BOOL fLargeFont) {
 /*
     Clear the screen, set the font, redraw the area
 */
     RECTL rclFrame;

     ClearScreen();
     AvioLargeFont(fLargeFont);
     WinQueryWindowRect(hWndFrame, &rclFrame);
     UpdateScrollBars(rclFrame);
     Update(0, cyChPS, 0);
 }

 void AvioScroll(USHORT SB_Command, USHORT Position, BOOL Horizontal) {
 /*
     Process the scroll bar messages

     These routines are symmetric; in fact, SB_LINELEFT = SB_LINEUP, etc...
     so one might note that this could be condensed.  It's left expanded for
     speed and clarity.  The scrollbar values are bounded to stay inside
     the Presentation Space.
 */
     if (Horizontal) {  /* Horizontal Scroll Bar */
         switch (SB_Command) {
             case SB_LINELEFT:
                 iCurCol = LowerBound(iCurCol, 1, 0); break;
             case SB_LINERIGHT:
                 iCurCol = UpperBound(iCurCol, 1, iMaxHorz); break;
             case SB_PAGELEFT:
                 iCurCol = LowerBound(iCurCol, cxChPage, 0); break;
             case SB_PAGERIGHT:
                 iCurCol = UpperBound(iCurCol, cxChPage, iMaxHorz); break;
             case SB_SLIDERTRACK:
                 iCurCol = (SHORT) Position;
             default: break;
         }
         if (SB_Command != SB_SLIDERTRACK)
             SetScroll(hWndHorzSB, iCurCol, iMaxHorz);

     } else { /* Vertical Scroll Bar */
         switch (SB_Command) {
             case SB_LINEUP:
                 iTopLine = LowerBound(iTopLine, 1, 0); break;
             case SB_LINEDOWN:
                 iTopLine = UpperBound(iTopLine, 1, iMaxVert); break;
             case SB_PAGEUP:
                 iTopLine = LowerBound(iTopLine, cyChPage, 0); break;
             case SB_PAGEDOWN:
                 iTopLine = UpperBound(iTopLine, cyChPage, iMaxVert);break;
             case SB_SLIDERTRACK:
                 iTopLine = (SHORT) Position;
             default: break;
         }
         if (SB_Command != SB_SLIDERTRACK)
             SetScroll(hWndVertSB, iTopLine, iMaxVert);
     }
     Refresh();
 }

 MRESULT AvioSize(HWND hWnd, USHORT msg, MPARAM mp1, MPARAM mp2) {
 /*
     Do the default AVIO sizing, and kyfe a few values
 */
     RECTL rclFrame;

     if (!fCreatedPS) return 0L;
     /*
         Update the scroll bars, and the screen
     */
     WinQueryWindowRect(hWndFrame, &rclFrame);
     UpdateScrollBars(rclFrame);
     /*
         Now, do the normal AVIO processing
     */
     return WinDefAVioWindowProc(hWnd, msg, mp1, mp2);
 }

 void Update(USHORT usLineNum, USHORT usHowMany, USHORT usStartLine) {
 /*
     Updates N lines starting from START line on screen.
     Starts at saved line LINENUM.
 */
     USHORT        i;                                /* Loop index variable */
     USHORT        usWhichLine = usLineNum;        /* Line number to be querie
     char        *szLine;

     for (i = usStartLine; i < (usStartLine + usHowMany); i++) {
         szLine = (*pfnQueryLine)(usWhichLine++);        /* Get the line */
         if (szLine) Blast(szLine, i, 0);                /* Print it out */
     }
 }

 void Refresh(void) {
 /*
     Do the origin shifting and screen updating
 */
     SHORT  Delta;
     int static iOldTopLine = 0;

     VioSetOrg(0, iCurCol, hVPS); /* Get the free AVIO horizontal shift */
     Delta = iTopLine - iOldTopLine; /* Compute vertical shift */
     if (Abs(Delta) < cyChPS) {
         if (Delta < 0) {         /* Scroll Up -- make Delta positive*/
             ScrollDown(-Delta);
             Update(iTopLine, -Delta, 0);
         } else {                /* Scroll Down by Delta */
             ScrollUp(Delta);
             Update(iTopLine + cyChPS - Delta, Delta, cyChPS - Delta);
         }
     } else AvioRedraw();        /* Redo the entire screen */
     iOldTopLine = iTopLine;
 }

 void AvioClose(void) {
 /*
     Termination routines
 */
     /*
         Destroy the Presentation Space
     */
     VioAssociate(NULL, hVPS);
     VioDestroyPS(hVPS);
     fCreatedPS = FALSE;
 }

 void AvioPaint(HWND hWnd) {
 /*
     Paint the AVIO presentation space by telling it to show itself.
     A possible optimization here is to repaint only the update region.
 */
     static HPS         hPS;
     static RECTL RectL;

     hPS = WinBeginPaint(hWnd, (HPS) NULL, &RectL);
     VioShowPS(cyChPS, cxChPS, 0, hVPS);
     WinEndPaint(hPS);
 }

 MRESULT AvioMinMax(PSWP pSWP) {
 /*
     Handle WM_MINMAX messages, to make sure frame doesn't get too big
 */
     if (pSWP->fs & (SWP_MAXIMIZE | SWP_RESTORE)) {
         if (pSWP->fs & SWP_MAXIMIZE) {
             /*
                 Save cx, cy values for later origin displacement
             */
             int Oldcx = pSWP->cx;
             int Oldcy = pSWP->cy;
             /*
                 Displace, and change to maximum size
             */
             pSWP->x += (Oldcx -
                 (pSWP->cx = cxMaxFrame + (int) (Value(SV_CXSIZEBORDER) << 1))
             pSWP->y += (Oldcy -
                 (pSWP->cy = cyMaxFrame + (int) (Value(SV_CYSIZEBORDER) << 1))
         }
         /*
             Now, fix the scroll bars
         */
         AvioAdjustFramePos(pSWP);
         return (MRESULT) TRUE;
     }
     return FALSE;
 }

 void AvioClear(void) { ClearScreen(); }

 void AvioAdjustFramePos(PSWP pSWP) {
 /*
     Trap WM_ADJUSTWINDOWPOS messages to the frame with this routine.
     Keep the window sized right, and control scrollbar visibility.
 */
     RECTL rclFrame;

     if (!(pSWP->cx && pSWP->cy)) return;         /* Null area... */
     if (pSWP->fs & SWP_MINIMIZE) return;        /* Iconic... */
     /*
         Make sure the dimensions are in range
     */
     pSWP->cx = Min(pSWP->cx, (cxMaxFrame + (SHORT)(Value(SV_CXSIZEBORDER)<<1)
     pSWP->cy = Min(pSWP->cy, (cyMaxFrame + (SHORT)(Value(SV_CYSIZEBORDER)<<1)
     /*
         Update the scroll bars
     */
     rclFrame.xLeft        = (LONG) pSWP->x;
     rclFrame.xRight        = (LONG) (pSWP->x + pSWP->cx - 1);
     rclFrame.yBottom        = (LONG) pSWP->y;
     rclFrame.yTop        = (LONG) (pSWP->y + pSWP->cy - 1);
     UpdateScrollBars(rclFrame);

     return;
 }

 void AvioTrackFrame(HWND hWnd, MPARAM mpTrackFlags) {
 /*
     Takes action on WM_TRACKFRAME message
 */
     static TRACKINFO tiTrackInfo;
     /*
         Get the tracking information in the TrackInfo structure
     */
     WinSendMsg(hWnd, WM_QUERYTRACKINFO, mpTrackFlags, &tiTrackInfo);
     WinTrackRect(hWnd, NULL, &tiTrackInfo);
 }

 void AvioQueryTrackInfo(PTRACKINFO pTI) {
 /*
     Routine which processes WM_QUERYTRACKINFO messages to the frame.
     Call this routine after the default one to change various parameters.

     Note:  In reality, since we have a menu bar, we should make the
     minimum width of the window something such that it does not "fold."
 */
     BOOL fMove;
     /*
         Get the grid set up for byte alignment (unless moving)

         cxGrid is set to half character width so that arrow keys
         will function when sizing (they try to size by half a
         character)
     */
     fMove = ((pTI->fs & TF_MOVE) == TF_MOVE);
     pTI->fs     |= TF_GRID;
     pTI->cxGrid  = (fMove) ? 1 : ((SHORT) lChWidth);
     pTI->cyGrid  = (fMove) ? 1 : ((SHORT) lChHeight);
     pTI->cxKeyboard        =        (SHORT) lChWidth;
     pTI->cyKeyboard        =        (SHORT) lChHeight;
     /*
         Bound the frame.
         Maximum:        Sizing Border, Scrollbars, Title, Menus, client regio
     */
     pTI->ptlMaxTrackSize.x = (LONG) (pTI->cxBorder << 1) + (LONG) cxMaxFrame;
     pTI->ptlMaxTrackSize.y = (LONG) (pTI->cyBorder << 1) + (LONG) cyMaxFrame;
 }

 void AvioRedraw(void) {
 /*
     Clear, then redraw the entire Presentation Space
 */
     ClearScreen();
     Update(iTopLine, cyChPS, 0);
 }

 MRESULT CALLBACK AvioClientWndProc
         (HWND hWnd, USHORT msg, MPARAM mp1, MPARAM mp2) {
 /*
      Window Procedure which traps messages to the Client area
 */
      switch (msg) {
           case WM_PAINT:                /* Paint the AVIO way! */
                 AvioPaint(hWnd);
                 break;

           case WM_SIZE:                        /* Size the AVIO way!  */
                 return AvioSize(hWnd, msg, mp1, mp2);
                 break;

           case WM_HSCROLL:
                 AvioScroll(HIUSHORT(mp2), LOUSHORT(mp2), TRUE);
                 break;

           case WM_VSCROLL:
                 AvioScroll(HIUSHORT(mp2), LOUSHORT(mp2), FALSE);
                 break;

           case WM_ERASEBACKGROUND:
                 break;

           case WM_TRACKFRAME:
                 AvioTrackFrame(hWnd, mp1);
                 break;

           case WM_MINMAXFRAME:                /* Limit Maximized window size
                 AvioMinMax((PSWP) mp1);

                 /* fall through */

           default: return (*pfnOldClient)(hWnd, msg, mp1, mp2);
      }
      return 0;
 }

 MRESULT CALLBACK AvioFrameWndProc(HWND hWnd, USHORT msg, MPARAM mp1, MPARAM m
 /*
     Force the frame to stay small enough (no larger than the PS)
 */
 {
     BOOL rc;                /* Return code from WM_QUERYTRACKINFO */

     switch(msg) {
         case WM_ADJUSTWINDOWPOS:        /* Calculate scroll bar adjustments *
             AvioAdjustFramePos(mp1);
             break;

         case WM_QUERYTRACKINFO:                /* Get default, then process m
             rc = (BOOL) SHORT1FROMMR((*pfnOldFrame)(hWnd, msg, mp1, mp2));
             AvioQueryTrackInfo((PTRACKINFO) mp2);
             return (MRESULT) rc;

         default: break;
     }
     return (*pfnOldFrame)(hWnd, msg, mp1, mp2);
 }

 void UpdateScrollBars(RECTL rclClient) {
 /*
     This routine fixes up the scroll bars when the window is resized, or
     when the font size is changed.

     Parameters:        The dimensions of the frame window
     Result:        Updates the scrollbars, enabling/disabling as needed
 */
     BOOL    fNeededHorz = fNeedHorz;  /* Did we need the scrollbars then? */
     BOOL    fNeededVert = fNeedVert;
     /*
         Compute the client rectangle, without the scrollbars
     */
     WinCalcFrameRect(hWndFrame, &rclClient, TRUE);
     /*
         Compute page width -- do we need the horizontal scroll bar?
     */
     cxChPage         = (int) CalcChars(lcx(rclClient), lChWidth);
     fNeedHorz = ((iMaxHorz = Max(cxChScreen - cxChPage,  0)) > 0);
     /*
         Compute page height -- do we need the vertical scroll bar?
     */
     cyChPage         = (int) CalcChars(lcy(rclClient), lChHeight);
     fNeedVert = ((iMaxVert = Max(cyChScreen - cyChPage, 0)) > 0);
     /*
         Maintain scrollbar integrity
     */
     iCurCol        = Min(iCurCol, iMaxHorz);
     iTopLine        = Min(iTopLine, iMaxVert);
     /*
         Now, update the scrollbars as necessary
     */
     FixScroll(fNeededHorz, fNeedHorz, hWndHorzSB,
               FCF_HORZSCROLL, &iCurCol, iMaxHorz, &cHUpdate);

     FixScroll(fNeededVert, fNeedVert, hWndVertSB,
               FCF_VERTSCROLL, &iTopLine, iMaxVert, &cVUpdate);
     /*
         Now, update the screen to be visually consistent
     */
     Refresh();
 }

 void FixScroll(fNeeded, fNeed, hWnd, flScroll, piPos, iMax, pc)
 /*
     This routine makes the necessary scrollbar adjustments, and
     also enables/disables them.
 */
 BOOL        fNeeded;            /* Whether we used to need the scrollbar */
 BOOL        fNeed;                    /* Whether we need the scrollbar now */
 HWND        hWnd;                    /* Handle to the scrollbar window */
 ULONG        flScroll;            /* FCF_xxxxSCROLL flag (for the scrollbar)
 int        *piPos;             /* Current location of scrollbar thumb */
 int        iMax;                    /* New maximum for the scrollbar */
 int        *pc;                    /* Counter for WinEnableWindowUpdate recur
 {
     if (fNeed) {    /* Enable the scroll bar -- we didn't need it before */
         if (!fNeeded) {
             EnableSB(hWnd);
             UpdateOff((*pc), hWnd);
             UpdateFrame(flScroll);
             UpdateOn((*pc), hWnd);
         }
         SetScroll(hWnd, (*piPos) = Min((*piPos), iMax), iMax);
     } else {            /* Disable the scroll bar, we no longer need it */
         if (fNeeded) {
             DisableSB(hWnd);
             UpdateOff((*pc), hWnd);
             UpdateFrame(flScroll);
             UpdateOn((*pc), hWnd);
         }
     }
 }

 void AvioLargeFont(BOOL fLargeFont) {
     static BOOL fFirst = TRUE;                                    // Need to
     static LONG lSmallHt, lSmallWd, lLargeHt, lLargeWd;     // Font sizes
     SWP swp;

     if (fFirst) {
         /*
             The first time through, get the small and large font sizes
         */
         DevQueryCaps(hDC, CAPS_CHAR_HEIGHT,                1L, &lLargeHt);
         DevQueryCaps(hDC, CAPS_CHAR_WIDTH,                1L, &lLargeWd);
         DevQueryCaps(hDC, CAPS_SMALL_CHAR_HEIGHT,        1L, &lSmallHt);
         DevQueryCaps(hDC, CAPS_SMALL_CHAR_WIDTH,        1L, &lSmallWd);
         fFirst = FALSE;
     }
     /*
         Set the character size with VioSetDeviceCellSize
     */
     SetCellSize( (SHORT) (lChHeight = ((fLargeFont) ? lLargeHt : lSmallHt)),
                  (SHORT) (lChWidth  = ((fLargeFont) ? lLargeWd : lSmallWd)) )
     /*
         Compute maximum size of client area
     */
     cxMaxClient        = (cxChPS * (SHORT) lChWidth);
     cxMaxFrame        = cxMaxClient + (SHORT) cxConstant;
     cyMaxClient = (cyChPS * (SHORT) lChHeight);
     cyMaxFrame  = cyMaxClient + (SHORT) cyConstant;
     /*
         Send a WM_ADJUSTFRAMEPOS message
     */
     WinQueryWindowPos(hWndFrame, &swp);
     if (swp.fs & SWP_MAXIMIZE) {
         AvioMinMax(&swp);
         WinSetMultWindowPos(hWndFrame, &swp, 1);
     } else {
         swp.fs = SWP_ACTIVATE | SWP_MOVE | SWP_SHOW | SWP_SIZE;
         WinSetWindowPos(hWndFrame, NULL, swp.x, swp.y,
             Min(cxMaxFrame, swp.cx), Min(cyMaxFrame, swp.cy), swp.fs);
     }
     AvioAdjustFramePos(&swp);                /* Fix up the frame, scroll bars
     AvioPaint(hWndClient);                /* Repaint with new characters   */
 }


 AVIO.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\COMTALK\AVIO.C

 /*
         avio.c -- AVIO handling routines
         For a cleaner implementation, look at the BROWSE application.

         Implements scrollbars, sets up an AVIO Presentation Space
         Intrinsically linked with a circular queue routine

         Created by Microsoft Corporation, 1989
 */

 #define  INCL_AVIO
 #define         INCL_DEV
 #define  INCL_VIO
 #define         INCL_WIN
 #include <os2.h>
 #include "global.h"
 "circleq.h"        /* Get Circular Buffer routines */
 "avio.h"        /* Prototype our routines */
 <stdio.h>        /* Needed to open LOG file */
 /*
     Constants
 */
 AVIO_PS_ROWS        25        /* Dimensions of the AVIO PS */
 #define AVIO_PS_COLUMNS        MAXLINELEN
        CATTRBYTES        1        /* 1 or 3 attribute bytes/cell */
        DEFPAGEWIDTH        5        /* Default pagesizes */
 #define        DEFPAGEHEIGHT        5

 char        Blank[2] = { 0x20, 0x07 };

 /*
     Macros to make the code more readable
 */
 /* Upper and Lower Bound Calculations */
 #define        Abs(a)                (((a) > 0) ? (a) : (-(a)))
 #define LowerBound(pos, disp, lbound) Max(pos - disp, lbound)
 #define UpperBound(pos, disp, ubound) Min(pos + disp, ubound)

 /* Scroll Bar Abbreviations */

 #define        DisableSB(hSB)        WinSetParent(hSB,  HWND_OBJECT, FALSE)
 #define        EnableSB(hSB)        WinSetParent(hSB, hWndSBParent, FALSE)
 #define        HBarHeight()        (fNeedHorz ? lHSBHeight : 0L)
 #define        VBarWidth()        (fNeedVert ? lVSBWidth  : 0L)
 #define SetScroll(h, pos, max) \
     WinSendMsg(h, SBM_SETSCROLLBAR, MPFROM2SHORT(pos, 0), MPFROM2SHORT(0, max
 #define        UpdateFrame(sb)        \
     WinSendMsg(hWndSBParent, WM_UPDATEFRAME, MPFROMLONG(sb), 0L)
 #define        UpdateOff(w)        WinEnableWindowUpdate(w, FALSE)
 #define        UpdateOn(w)        WinEnableWindowUpdate(w, TRUE)

 /* Scrolling Macros */
 #define        ClearScreen()        ScrollUp(-1)
 #define ScrollDown(n)        VioScrollDn(0, 0, -1, -1, n, Blank, hVPS)
 #define        ScrollUp(n)        VioScrollUp(0, 0, -1, -1, n, Blank, hVPS)
 #define        SetCursor(x, y)        VioSetCurPos((USHORT) x, (USHORT) y, hV

 /* Miscellaneous */
 /*
     If partial ANSI emulation is desired, use:
         VioSetCurPos((USHORT) x, (USHORT) y, hVPS); \
         VioWrtTTY(l->szText, l->cch, hVPS)
 */
 #define Blast(l, x, y)        VioWrtCharStr(l->szText, l->cch, x, y, hVPS)
 /*
     Calculate the number of characters in a page
     For nicer behavior, you can do rounding here
 */
 #define CalcChars(pPg, pCh, default) \
         ((pCh) ? (Max((int) ((pPg) / ((SHORT) pCh)), 0)) : (default))
 #define        Value(value)        WinQuerySysValue(HWND_DESKTOP, value)
 /*
     File-Local Variables
 */
 HDC        hDC;                /* Device Context */
 HVPS        hVPS;                /* Virtual PS */
 int        iTopLine;        /* PS Line of window corner */
 int        iCurCol;         /* Current column of window corner */
 int        cchPgWidth;        /* Width and height of our window */
 int        cchPgHeight;
 int        cchMaxHorz;        /* Scroll bar upper bounds */
 int        cchMaxVert;
 BOOL        fNeedHorz;        /* Do we need the scroll bars or not? */
 BOOL        fNeedVert;
 HWND        hWndHScroll;        /* Window handles of ScrollBar windows */
 HWND        hWndVScroll;
 HWND        hWndSBParent;        /* Could mooch off the value in main(), but
 /*
     Measurements used to help make the window look nice
 */
 LONG        lChWidth,   lChHeight;                        /* Character size *
 LONG        lHSBHeight, lVSBWidth;                        /* Scrollbar measur
 LONG        lMiscWidth, lMiscHeight;                /* Border, titlebar, ...
 int        iMaxWidth,  iMaxHeight;                        /* Client area boun
 int        iMaxFrameWidth, iMaxFrameHeight;        /* Frame window bounds */
 BOOL        fCreated;                                /* AVIO PS created */
 int        rc;                                        /* Return code */
 VIOCURSORINFO vci;
 /*
    Local prototypes
 */
 void GetMeasurements(void);
 void Update(USHORT, USHORT, USHORT, BOOL);
 void Refresh(BOOL);
 void WantCursor(BOOL);
 void SetScrollPos(void);
 void SetScrollPosHorz(void);
 void SetScrollPosVert(void);
 /*
     The actual routines
 */
 void GetMeasurements(void) {
 /*
     Get display parameters
 */
     /*
         Scroll bar widths and heights
     */
     lHSBHeight        = Value(SV_CYHSCROLL);
     lVSBWidth        = Value(SV_CXVSCROLL);
     /*
         Non-PS widths and heights
     */
     lMiscHeight        = (Value(SV_CYSIZEBORDER) << 1)        /* A border on
                 + Value(SV_CYTITLEBAR)                /* The title bar...
                 + Value(SV_CYMENU)                /* ...and the menu bar   */
                 + Value(SV_CYBYTEALIGN);        /* ...and alignment         *

     lMiscWidth        = (Value(SV_CXSIZEBORDER) << 1);/* A border on each sid
     /*
         Height and width of characters
     */
     rc = DevQueryCaps(hDC, CAPS_CHAR_HEIGHT, 1L, &lChHeight);
     rc = DevQueryCaps(hDC, CAPS_CHAR_WIDTH,  1L, &lChWidth);
     /*
         Compute size of client and frame windows
     */
     iMaxWidth                = (AVIO_PS_COLUMNS        * (int) lChWidth);
     iMaxHeight                = (AVIO_PS_ROWS                * (int) lChHeigh
     iMaxFrameWidth        = (iMaxWidth                + (int) lMiscWidth);
     iMaxFrameHeight        = (iMaxHeight                + (int) lMiscHeight);
     /*
         Compute cursor attributes
     */
     vci.yStart        = (USHORT) 0;
     vci.cEnd        = (USHORT) lChHeight - 1;
     vci.cx        = 0;
 }

 void AvioInit(HWND hWndFrame, HWND hWndClient) {
 /*
     Initialize Presentation Space, Device Context, Scroll Bars
 */
     /*
         Create the AVIO Presentation Space
     */
     hDC = WinOpenWindowDC(hWndClient);
     VioCreatePS(&hVPS, AVIO_PS_ROWS, AVIO_PS_COLUMNS, 0, CATTRBYTES, 0);
     VioAssociate(hDC, hVPS);
     fCreated = TRUE;
     /*
         Turn on the cursor and home it
     */
     WantCursor(TRUE);
     SetCursor(0, 0);
     /*
         Snag scroll bar info
     */
     hWndHScroll  = WinWindowFromID(hWndFrame,  FID_HORZSCROLL);
     hWndVScroll  = WinWindowFromID(hWndFrame,  FID_VERTSCROLL);
     hWndSBParent = WinQueryWindow(hWndHScroll, QW_PARENT, FALSE);
     fNeedHorz         = fNeedVert  = TRUE;
     /*
         Get character height in pixels, etc...
     */
     GetMeasurements();
 }

 void AvioStartup(HWND hWndClient) {
     SWP swp;
     /*
         Initialize the queue
     */
     QueInit();
     /*
         Initialize the screen
     */
     ClearScreen();
     WinQueryWindowPos(hWndClient, &swp);
     AvioSize(hWndClient, WM_NULL, NULL, MPFROM2SHORT(swp.cx, swp.cy));
 }

 void AvioScroll(USHORT SB_Command, USHORT usPosition, BOOL fHorizontal) {
 /*
     Process the scroll bar messages

     These routines are symmetric; in fact, SB_LINELEFT = SB_LINEUP, etc...
     so one might note that this could be condensed.  It's left expanded for
     speed and clarity.  I bound the values each way so that we stay inside
     the AVIO presentation space.
 */
     if (fHorizontal) {  /* Horizontal Scroll Bar */
         switch (SB_Command) {
             case SB_LINELEFT:
                 iCurCol = LowerBound(iCurCol, 1, 0); break;
             case SB_LINERIGHT:
                 iCurCol = UpperBound(iCurCol, 1, cchMaxHorz); break;
             case SB_PAGELEFT:
                 iCurCol = LowerBound(iCurCol, cchPgWidth, 0); break;
             case SB_PAGERIGHT:
                 iCurCol = UpperBound(iCurCol, cchPgWidth, cchMaxHorz); break;
             case SB_SLIDERTRACK:
                 iCurCol = (SHORT) usPosition;
             default: break;
         }
         if (SB_Command != SB_SLIDERTRACK)
             SetScroll(hWndHScroll, iCurCol, cchMaxHorz);

     } else { /* Vertical Scroll Bar */
         switch (SB_Command) {
             case SB_LINEUP:
                 iTopLine = LowerBound(iTopLine, 1, 0); break;
             case SB_LINEDOWN:
                 iTopLine = UpperBound(iTopLine, 1, cchMaxVert); break;
             case SB_PAGEUP:
                 iTopLine = LowerBound(iTopLine, cchPgHeight, 0); break;
             case SB_PAGEDOWN:
                 iTopLine = UpperBound(iTopLine, cchPgHeight, cchMaxVert); bre
             case SB_SLIDERTRACK:
                 iTopLine = (SHORT) usPosition;
             default: break;
         }
         if (SB_Command != SB_SLIDERTRACK)
             SetScroll(hWndVScroll, iTopLine, cchMaxVert);
     }
     Refresh(FALSE);
 }

 MRESULT AvioSize(HWND hWnd, USHORT msg, MPARAM mp1, MPARAM mp2) {
 /*
     Do the default AVIO sizing, and kyfe a few values
 */
     if (!fCreated) return 0L;
     /*
         Compute height and width of page in characters

         The scrollbars have already been subtracted out,
         since we are called by the client area.
     */
     cchPgHeight = CalcChars(SHORT2FROMMP(mp2), lChHeight, DEFPAGEHEIGHT);
     cchPgWidth  = CalcChars(SHORT1FROMMP(mp2), lChWidth,  DEFPAGEWIDTH);
     /*
         Adjust scrollbar maximums
     */
     cchMaxVert = Max(AVIO_PS_ROWS    - cchPgHeight, 0);
     cchMaxHorz = Max(AVIO_PS_COLUMNS -  cchPgWidth, 0);
     /*
         Maintain scrollbar integrity
     */
     fNeedHorz = (cchMaxHorz > 0);
     fNeedVert = (cchMaxVert > 0);
     SetScroll(hWndHScroll, iCurCol  = Min(iCurCol, cchMaxHorz), cchMaxHorz);
     SetScroll(hWndVScroll, iTopLine = Min(iTopLine,cchMaxVert), cchMaxVert);
     /*
         Do the Scroll Bar shifting
     */
     Refresh(FALSE);
     /*
         Now, do the normal AVIO processing
     */
     return WinDefAVioWindowProc(hWnd, msg, mp1, mp2);
 }

 void Update
     (USHORT usLineNum, USHORT usHowMany, USHORT usStartLine, BOOL fForced) {
 /*
     Updates usHowMany lines starting from usStartLine on screen.
     Starts at saved line usLineNum.  If fForced is set, all lines
     in range are displayed; otherwise it's lazy.
 */
     USHORT        i;                                /* Loop index */
     USHORT        usWhichLine = usLineNum;        /* Line to be queried */
     Line        l;                                /* Line to be output */

     for (i = usStartLine; i < (usStartLine + usHowMany); i++) {
         l = QueQuery(usWhichLine++);                /* Get the line */
         if (!l->fDrawn || fForced) {
             if (l->cch) Blast(l, i, 0);                /* Print it out */
             if (!l->fComplete) SetCursor(i, l->cch);
             l->fDrawn = TRUE;
         }
     }
 }

 void Refresh(BOOL fRedraw) {
 /*
     fRedraw forces full redraw if set
 */
     SHORT  sDelta;
     int static iOldTopLine = -AVIO_PS_ROWS;

     VioSetOrg(0, iCurCol, hVPS); /* Get the free AVIO horizontal shift */
     sDelta = iTopLine - iOldTopLine; /* Compute vertical shift */
     if ((Abs(sDelta) < AVIO_PS_ROWS) && !fRedraw) {
         if (sDelta < 0) {         /* Scroll Up -- make sDelta positive*/
             ScrollDown(-sDelta);
             Update(iTopLine, -sDelta, 0, TRUE);
         } else {                /* Scroll Down by sDelta */
             ScrollUp(sDelta);
             Update(iTopLine + cchPgHeight - sDelta, sDelta,
                                 cchPgHeight - sDelta, TRUE);
         }
     } else AvioRedraw();        /* Redo the entire screen */
     iOldTopLine = iTopLine;
 }

 void AvioClose (void) {
 /*
     Termination routines
 */
     /*
         Destroy the Presentation Space
     */
     VioAssociate(NULL, hVPS);
     VioDestroyPS(hVPS);
     fCreated = FALSE;
 }

 void AvioPaint(HWND hWnd) {
     static HPS   hPS;
     static RECTL rcl;

     hPS = WinBeginPaint(hWnd, NULL, &rcl);
     VioShowPS(AVIO_PS_ROWS, AVIO_PS_COLUMNS, 0, hVPS);
     WinEndPaint(hPS);
 }

 MRESULT AvioMinMax(PSWP pSWP) {
 /*
     Control Maximizing
 */
     if (pSWP->fs & (SWP_MAXIMIZE | SWP_RESTORE)) {
         if (pSWP->fs & SWP_MAXIMIZE) {
             /*
                 Save cx, cy values for later origin displacement
             */
             int iOldcx = pSWP->cx;
             int iOldcy = pSWP->cy;
             /*
                 Displace, and change to maximum size
             */
             pSWP->x += (iOldcx - (pSWP->cx = iMaxFrameWidth));
             pSWP->y += (iOldcy - (pSWP->cy = iMaxFrameHeight));
         }
         /*
             Now, fix the scroll bars
         */
         AvioAdjustFrame(pSWP);
         return (MRESULT) TRUE;
     }
     return FALSE;
 }

 void AvioClear(void) { ClearScreen(); }

 void AvioAdjustFrame(PSWP pSWP) {
 /*
     Trap WM_ADJUSTWINDOWPOS messages to the frame with this routine.
     Keep the window sized right, and control scrollbar visibility.
 */
     BOOL fNeededHorz = fNeedHorz;
     BOOL fNeededVert = fNeedVert;
 /*
     Do scrollbar enable/disable calculations (but don't update the screen)
 */
     if (pSWP->fs & SWP_MINIMIZE) fNeedHorz = fNeedVert = FALSE;
     if ((pSWP->cx * pSWP->cy) == 0) return;
     /*
         Do we need them?
     */
     fNeedVert = (pSWP->cy < (SHORT) (iMaxFrameHeight));
     fNeedHorz = (pSWP->cx < (SHORT) (iMaxFrameWidth  + VBarWidth()));
     fNeedVert = (pSWP->cy < (SHORT) (iMaxFrameHeight + HBarHeight()));
 /*
     Do width calculations to make sure we're staying small enough.
     The Tracking Rectangle shouldn't allow us to get too big.
 */
     /*
         Check if we're stretching too far
     */
     pSWP->cx = Min(pSWP->cx, iMaxFrameWidth  + (int) VBarWidth());
     pSWP->cy = Min(pSWP->cy, iMaxFrameHeight + (int) HBarHeight());
     /*
         ...if so, fix, then add them!
     */
     AvioSize(NULL, WM_NULL, NULL, MPFROM2SHORT(
         pSWP->cx - (int) (lMiscWidth + VBarWidth()),
         pSWP->cy - (int) (lMiscHeight + HBarHeight()) ));

     if (fNeedHorz) {
         if (!fNeededHorz) {
             EnableSB(hWndHScroll);
             UpdateOff(hWndHScroll);
             UpdateFrame(FCF_HORZSCROLL);
             UpdateOn(hWndHScroll);
         }
     } else {
         if (fNeededHorz) {
             DisableSB(hWndHScroll);
             UpdateOff(hWndHScroll);
             UpdateFrame(FCF_HORZSCROLL);
             UpdateOn(hWndHScroll);
         }
     }
     if (fNeedVert) {
         if (!fNeededVert) {
              EnableSB(hWndVScroll);
              UpdateOff(hWndVScroll);
              UpdateFrame(FCF_VERTSCROLL);
              UpdateOn(hWndVScroll);
         }
     } else {
         if (fNeededVert) {
              DisableSB(hWndVScroll);
              UpdateOff(hWndVScroll);
              UpdateFrame(FCF_VERTSCROLL);
              UpdateOn(hWndVScroll);
         }
     }
 }

 void AvioTrackFrame(HWND hWnd, MPARAM mpTrackFlags) {
 /*
     Takes action on WM_TRACKFRAME message
 */
     static TRACKINFO tiTrackInfo;
     /*
         Get the tracking information in the TrackInfo structure
     */
     WinSendMsg(hWnd, WM_QUERYTRACKINFO, mpTrackFlags, &tiTrackInfo);
     WinTrackRect(hWnd, NULL, &tiTrackInfo);
 }

 void AvioQueryTrackInfo(PTRACKINFO pTI) {
 /*
     Forces the frame to be byte aligned and bounded
 */
     BOOL fMove;
     /*
         Get the grid set up for byte alignment

         Set cxGrid to half a character width, because sizing
         from the keyboard tries to move by half characters.
         Also, make sure we can move the window freely.
     */
     fMove = ((pTI->fs & TF_MOVE) == TF_MOVE);
     pTI->fs     |= TF_GRID;
     pTI->cxGrid  = (fMove) ? 1 : ((SHORT) lChWidth);
     pTI->cyGrid  = (fMove) ? 1 : ((SHORT) lChHeight);
     pTI->cxKeyboard = (SHORT) lChWidth;
     pTI->cyKeyboard = (SHORT) lChHeight;
     /*
         Bound the frame now
     */
     pTI->ptlMinTrackSize.x = (pTI->cxBorder << 1) + lMiscWidth;
     pTI->ptlMinTrackSize.y = (pTI->cyBorder << 1) + lMiscHeight;
     pTI->ptlMaxTrackSize.x = iMaxFrameWidth  + lVSBWidth +  (pTI->cxBorder <<
     pTI->ptlMaxTrackSize.y = iMaxFrameHeight + lHSBHeight + (pTI->cyBorder <<
 }

 BOOL AvioUpdateLines(BOOL fPage, BOOL *fPaging) {
 /*
     Update the display
 */
     int        cLines;

     cLines = QueUpdateHead(AVIO_PS_ROWS, fPage, *fPaging);
     if (cLines == AVIO_PS_ROWS) *fPaging = TRUE;
     if (cLines > 0) {
         ScrollUp(cLines);
         Update(iTopLine + AVIO_PS_ROWS - cLines, cLines,
                         AVIO_PS_ROWS - cLines, TRUE);
     }
     Update(iTopLine, cchPgHeight, 0, FALSE);
     return TRUE;
 }

 void AvioRedraw(void) {
 /*
     Clear, then redraw the entire Presentation Space
 */
     ClearScreen();
     Update(iTopLine, cchPgHeight, 0, TRUE);
 }

 void WantCursor(BOOL fYes) {
 /*
     Do the underscore cursor
 */
     vci.attr        = (USHORT) (fYes ? 0 : -1);
     vci.yStart        = 0;
     vci.cEnd        = (USHORT) lChHeight - 1;
     vci.cx        = 0;
     VioSetCurType(&vci, hVPS);
 }

 void AvioPageUp(void) {
 /*
     Execute the Page Up instruction
 */
     int cLines;

     cLines = QuePageUp(AVIO_PS_ROWS);
     ScrollDown(cLines);
     Update(iTopLine, cLines, 0, TRUE);
 }



 BIGBEN.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BIGBEN\BIGBEN.C

 /*
  * This example uses a few of the many VIO calls.
  *
  * This example puts the time on the screen in large numbers.
  *
  * Created by Microsoft Corp. 1986
  */

 #include <os2def.h>
 #define INCL_DOSPROCESS
 #define INCL_DOSDATETIME
 #include <bsedos.h>
 #define INCL_SUB
 #include <bsesub.h>
 #include <stdio.h>

 #define        CHAR_WIDTH        8
 #define        CHAR_HEIGHT        7

        CLOCK_ROW        10        /* row to start the clock */
        TOTAL_COLMS        80        /* screen size in colms */
        TOTAL_ROWS        24        /* screen size in rows */


 char        BigChars[10][CHAR_HEIGHT][CHAR_WIDTH] = {

 {
         "   00  ",
         "  0  0 ",
         " 0    0",
         " 0    0",
         " 0    0",
         "  0  0 ",
         "   00  "
 },
 {
         "   1   ",
         "   1   ",
         "   1   ",
         "   1   ",
         "   1   ",
         "   1   ",
         "   1   "
 },
 {
         "  2222 ",
         " 2    2",
         "      2",
         "     2 ",
         "   2   ",
         "  2    ",
         " 222222"
 },
 {
         " 33333 ",
         "      3",
         "      3",
         "   333 ",
         "      3",
         "      3",
         " 33333 "
 },
 {
         "    44 ",
         "   4 4 ",
         "  4  4 ",
         " 4   4 ",
         " 444444",
         "     4 ",
         "     4 "
 },
 {
         " 555555",
         " 5     ",
         " 55555 ",
         "      5",
         "      5",
         " 5    5",
         "  5555 "
 },
 {
         "    6  ",
         "   6   ",
         "  6    ",
         "  6666 ",
         " 6    6",
         " 6    6",
         "  6666 "
 },
 {
         " 777777",
         "      7",
         "     7 ",
         "    7  ",
         "   7   ",
         "  7    ",
         " 7     "
 },
 {
         "  8888 ",
         " 8    8",
         " 8    8",
         "  8888 ",
         " 8    8",
         " 8    8",
         "  8888 "
 },
 {
         "  9999 ",
         " 9    9",
         " 9    9",
         "  9999 ",
         "    9  ",
         "   9   ",
         "  9    "
 }
 };


 main(argc, argv)
         int        argc;
         char        *argv[];
 {
         unsigned        rc;        /* return code */
         DATETIME Now;         /* time struct for DosGetDateTime */

         /* clear the screen */

         VioWrtNCell( " \07", TOTAL_ROWS * TOTAL_COLMS, 0, 0, 0 );

         /* paint separators between hours and minutes, and minutes and second

         VioWrtNCell( "|\07", 1, (CLOCK_ROW + 2), 27, 0 );
         VioWrtNCell( "|\07", 1, (CLOCK_ROW + 5), 27, 0 );
         VioWrtNCell( "|\07", 1, (CLOCK_ROW + 2), 52, 0 );
         VioWrtNCell( "|\07", 1, (CLOCK_ROW + 5), 52, 0 );

         for (;;) {

             /* get the system time */

             if (rc = DosGetDateTime( &Now))  {

                 printf("DosGetDateTime failed, error: %d\n", rc);
                 DosExit(EXIT_PROCESS, 0);
             }

             /* write the digits out to the screen */

             LoadNumber(Now.hours / 10, 5, CLOCK_ROW);
             LoadNumber(Now.hours % 10, 15, CLOCK_ROW);
             LoadNumber(Now.minutes / 10, 30, CLOCK_ROW);
             LoadNumber(Now.minutes % 10, 40, CLOCK_ROW);
             LoadNumber(Now.seconds / 10, 55, CLOCK_ROW);
             LoadNumber(Now.seconds % 10, 65, CLOCK_ROW);

             DosSleep(900L);
         }
 }


 /* display the digit at the given coordinates */

 LoadNumber( dig, x, y )
         unsigned        dig;
         unsigned        x;
         unsigned        y;
 {
         int        i;

         /* write a list of char strings to make up a display number */

         for (i=0; (i < CHAR_HEIGHT); i++)

             /* write a character string starting from the coordinates */

             VioWrtCharStr( BigChars[dig][i], CHAR_WIDTH, y++, x, 0);
 }


 BIO.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BIO\BIO.C

 /*  Biorhythm - Utility to compute personal biorhythm charts.
 *
 *   Created by Microsoft Corp., 1989
 *
 *   Purpose:
 *       Program entry point, initialization and GetMessage loop.
 *
 *   Arguments:
 *       None
 *
 *   Globals (modified):
 *       hAB           - Handle to the Anchor Block
 *       hMsgQ         - Handle to the application's message queue
 *       hwndAppFrame  - Window handle of parent window's frame
 *       hwndKidFrame  - Window handle of parent window's frame
 *       hwndApp       - Window handle of parent window's client area
 *       hwndKid       - Window handle of child window's client area
 *       szAppName[10] - RC file program name (Biorhythm).
 *       szKidName[10] - RC file child window name (Legend).
 *
 *   Globals (referenced):
 *       tmFontInfo    - Text Metric structure defined during WM_CREATE
 *
 *   Description:
 *       The theory of biorhythms states that life consists of three cycles,
 *       physical, emotional and intellectual of 23, 28 and 33 days,
 *       respectively.  The cycles each begin on the date of birth.
 *
 *   Limits:
 *       The intended use of this program is for the 20th and 21st centuries.
 *       The calculations of biorhythms will not be accurate outside of this
 *       range due to formulae used to compute days between dates.
 *
 */

 #define INCL_WIN
 #include <os2.h>

 #include <stddef.h>

 #include "bio.h"

 /* Write-once global variables */
 HAB     hAB;
 HMQ     hMsgQ;
 HWND    hwndApp, hwndKid;
 HWND    hwndAppFrame, hwndKidFrame;
 char    szAppName[10];
 char    szKidName[10];
 ULONG        AppCtlData = FCF_STANDARD | FCF_VERTSCROLL | FCF_NOBYTEALIGN & ~
 ULONG        KidCtlData = FCF_TITLEBAR;
 PFNWP        OldFrameWndProc;

 /* Read-only global variables */
 extern  FONTMETRICS     tmFontInfo;
 extern        SHORT                cxLegendField;
 extern        SHORT                cxDateField;

 SHORT cdecl main(  )
 {
     QMSG        qMsg;
     SHORT       dx, dy, x, y;
     SHORT        cxSizeBorder;
     SHORT        cySizeBorder;
     SHORT        cxBorder;
     SHORT        cyBorder;

     /* Standard initialization.  Get anchor block and message queue. */
     hAB   = WinInitialize(0);
     hMsgQ = WinCreateMsgQueue( hAB, 0 );

     /* Get string constants for parent and child window registration
        and creation from resource string table. */
     WinLoadString( hAB, (HMODULE) NULL, IDS_APPNAME, sizeof(szAppName), szApp
     WinLoadString( hAB, (HMODULE) NULL, IDS_KIDNAME, sizeof(szKidName), szKid

     /* Register parent window.  Terminate if error. */
     if ( !WinRegisterClass( hAB, szAppName, BioWndProc,
             CS_CLIPCHILDREN | CS_SIZEREDRAW, 0 ) )
         return( FALSE );

     /* Register child window.  Terminate if error. */
     if ( !WinRegisterClass( hAB, szKidName, KidWndProc, 0, 0 ) )
         return( FALSE );

     /* Create a parent window of class szAppName */
     hwndAppFrame = WinCreateStdWindow(
         HWND_DESKTOP,
         0L,
         &AppCtlData,
         szAppName,
         NULL,
         0L,
         (HMODULE) NULL,
         ID_BIO,
         (HWND FAR *)&hwndApp
         );

     /* Create a child window of class KidClass */
     hwndKidFrame = WinCreateStdWindow(
         hwndApp,
         FS_BORDER,
         &KidCtlData,
         szKidName,
         szKidName,
         0L,
         (HMODULE) NULL,
         0,
         (HWND FAR *)&hwndKid
         );

     /* Subclass frame so that minimum window size can be controled */
     OldFrameWndProc = WinSubclassWindow( hwndAppFrame, FrameWndProc );

     /* Get the size of the screen and border.  Used to place and size window
     cxSizeBorder =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CXSIZEBORDER );
     cySizeBorder =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CYSIZEBORDER );
     cxBorder         =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CXBORDER );
     cyBorder         =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CYBORDER );
     x                 =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CXSCREEN )
     y                 =  (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CYSCREEN )

     /* Calculate width and height of child window.  Must be able to
        display three lines and wide enough for text and corresponding colored
        line.  Must take into account titlebar and border vertical sizes. */
     dx = cxLegendField * 2;
     dy = (SHORT)(tmFontInfo.lMaxBaselineExt*3 +
          WinQuerySysValue( HWND_DESKTOP, SV_CYTITLEBAR ) +
          WinQuerySysValue( HWND_DESKTOP, SV_CYBORDER ) * 2);

     /* Place and size parent and child windows, then make them visible.
        WinCreateStdWindow does not include position and size arguments.
        Parent window is thin, but full screen high.  Child window is placed
        10 pixels over and up from the parent window's lower left corner. */
     WinSetWindowPos( hwndAppFrame, NULL,
                      x-(3*cxDateField)+cxSizeBorder,
                      -cySizeBorder,
                      (3*cxDateField),
                      y+2*cySizeBorder,
                      SWP_MOVE | SWP_SIZE | SWP_ACTIVATE | SWP_SHOW );
     WinSetWindowPos( hwndKidFrame, NULL, 10, 10, dx, dy,
                      SWP_MOVE | SWP_SIZE | SWP_ACTIVATE | SWP_SHOW );

     /* Get messages from application queue and dispatch them for processing *
     while( WinGetMsg( hAB, &qMsg, (HWND)NULL, 0, 0 ) )
     {
         WinDispatchMsg( hAB, &qMsg );
     }

     /* Clean up.  All child windows will be destoyed automatically */
     WinDestroyWindow( hwndAppFrame );
     WinDestroyMsgQueue( hMsgQ );
     WinTerminate( hAB );
 }


 BIOCMD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BIO\BIOCMD.C

 /*  BioDlg() - Dialog Box routine.
 *
 *   Created by Microsoft Corporation, 1989
 *
 *   Purpose:
 *       Allow setting of birthdate and viewing date for biorhythm display.
 *
 *   Arguments:
 *       hDlg          - Handle of Dialog Box owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 *
 *   Globals (modified):
 *       Born          - Birthdate in julian days.  Read from OS2.INI.
 *       SelectDay     - Current day being tracked, day is highlighted.
 *                        This is stored as # days from birthdate.
 *                        This is initialized to the current date in WM_CREATE
 *       Day           - Day number from date born which is top line being
 *                       displayed.  Initially three days before SelectDay.
 *       bBorn         - Boolean indicating whether valid birtdate entered or
 *                       defined in OS2.INI.  Nothing graphed until valid.
 *
 *   Globals (referenced):
 *       hAB           - Handle to the Anchor Block
 *       szAppName[]   - RC file program name (Biorhythm).
 *
 *   Description:
 *       Biorythm cycles start on the date of birth and the state of
 *       of these cycles may be viewed on the selected date.  A check
 *       box is provided to update (record) the birthdate in the WIN.INI
 *       file so that it will be automatically available in subsequent
 *       sessions.
 *
 *   Limits:
 *       Minor error checking is provided when OK is selected to make
 *       sure that the dates specified fall in the 20th and 21st
 *       centuries.  No error checking is attempted to verify correct
 *       month or day of month entries.
 *
 */

 #define INCL_WIN
 #include <os2.h>

 #include "bio.h"
 #include <math.h>
 #include <stdio.h>

 /* Read-only global variables */
 extern HAB      hAB;
 extern char     szAppName[];

 /* Global variables (modified) */
 extern long     SelectDay, Day;
 extern double   Born;
 extern BOOL     bBorn;

 /* Function prototypes */
 void InitBioDlg(HWND);
 void BioDlgCmd(HWND, MPARAM);

 MRESULT CALLBACK BioDlg( hDlg, message, mp1, mp2 )
 HWND    hDlg;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     switch( message ) {
         case WM_INITDLG:
             InitBioDlg(hDlg);
             break;

         case WM_COMMAND:
             BioDlgCmd(hDlg, mp1);
             break;

         default:
             return( WinDefDlgProc( hDlg, message, mp1, mp2 ) );

     }
     return 0L;
 }


 /*  About() - General purpose About dialog box.
 *
 *   Purpose:
 *       Provide program propoganda.
 *
 *   Arguments:
 *       hDlg          - Handle of Dialog Box owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 */

 MRESULT CALLBACK About( hWndDlg, message, mp1, mp2 )
 HWND   hWndDlg;
 USHORT message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     switch( message )
     {
       case WM_COMMAND:
         switch( LOUSHORT( mp1 ) )
         {
           case DID_OK:
             WinDismissDlg( hWndDlg, TRUE );
             break;

           default:
             break;
         }
         break;

       default:
         return( WinDefDlgProc( hWndDlg, message, mp1, mp2 ) );
     }
     return( FALSE );
 }


 void InitBioDlg(HWND hDlg) {
 /*
      If valid OS2.INI info, fill in birthdate edit fields
 */
     USHORT        year, month;
     double      day;

     if (bBorn) {
       calendar( Born, (int *)&year, (int *)&month, &day );
       WinSetDlgItemShort( hDlg, ID_BDYEAR, year, FALSE );
       WinSetDlgItemShort( hDlg, ID_BDMONTH, month, FALSE );
       WinSetDlgItemShort( hDlg, ID_BDDAY, (int)day, FALSE );
     }
     /* Display current date or date highlighted */
     calendar( Born+SelectDay, (int *)&year, (int *)&month, &day );
     WinSetDlgItemShort( hDlg, ID_YEAR, year, FALSE );
     WinSetDlgItemShort( hDlg, ID_MONTH, month, FALSE );
     WinSetDlgItemShort( hDlg, ID_DAY, (int)day, FALSE );
 }


 void BioDlgCmd(HWND hDlg, MPARAM mp1) {
 /*
     Bio Dialog Box routine WM_COMMAND processor
 */
     USHORT        year, month, iDay;
     double      day;
     char        szBuf[10];

     switch( LOUSHORT( mp1 ) ) {
         case DID_OK:
             /* Get the birthday edit field values */
             WinQueryDlgItemShort( hDlg, ID_BDYEAR, &year, FALSE );
             WinQueryDlgItemShort( hDlg, ID_BDMONTH, &month, FALSE );
             WinQueryDlgItemShort( hDlg, ID_BDDAY, &iDay, FALSE );
             day = (double)iDay;
             /* Check that date is within acceptable range */
             if (year<1900 || year>2100) {
                WinMessageBox( HWND_DESKTOP, hDlg,
                               "Dates valid from 1900-2100",
                               "Birthday!", 0,
                               MB_OK | MB_ICONEXCLAMATION );
                break;
             }
             /* Get julian date of birth date */
             Born = julian( year, month, day );

             /* Write birth date to OS2.INI if check box checked */
             if (WinSendDlgItemMsg(hDlg, ID_OS2INI, BM_QUERYCHECK, 0L, 0L)) {
                    sprintf(szBuf, "%d", year);
               WinWriteProfileString( hAB, szAppName, "Year", szBuf );
                    sprintf(szBuf, "%d", month);
               WinWriteProfileString( hAB, szAppName, "Month", szBuf );
                    sprintf(szBuf, "%d", (int)day);
               WinWriteProfileString( hAB, szAppName, "Day", szBuf );
             }

             /* Get selected day of interest edit field values */
             WinQueryDlgItemShort( hDlg, ID_YEAR, &year, FALSE );
             WinQueryDlgItemShort( hDlg, ID_MONTH, &month, FALSE );
             WinQueryDlgItemShort( hDlg, ID_DAY, &iDay, FALSE );
             day = (double)iDay;
             /* Check that date is within acceptable range */
             if (year<1900 || year>2100) {
                WinMessageBox( HWND_DESKTOP, hDlg,
                               "Dates valid from 1900-2100",
                               "Display Date!", 0,
                               MB_OK | MB_ICONEXCLAMATION );
                break;
             }

             /* Compute number of days since birth */
                  SelectDay  = (long)(julian( year, month, day ) - Born);
             /* Top date of display is 3 days before selected day */
             Day = SelectDay - 3;
             /* Got a valid birthdate, enable all routines */
             bBorn = TRUE;
             WinDismissDlg( hDlg, TRUE );
             break;

         case DID_CANCEL:
             /* Exit and ignore entries */
             WinDismissDlg( hDlg, FALSE );
             break;

         default:
             break;
     }
 }


 BIOPAINT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BIO\BIOPAINT.C

 /*
     biopaint.c        -   WM_PAINT processing and calendar conversion routine

     Created by Microsoft Corporation, 1989
 */
 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 #include "bio.h"
 #include <math.h>
 #include <stdio.h>

 /* Read-only global variables */
 extern double   Born;
 extern long     Day, SelectDay;
 extern BOOL     bBorn;
 extern FONTMETRICS tmFontInfo;
 extern int      LinesPerPage;
 extern RECTL    rclClient;
 extern SHORT        cxDateField;

 /* Read-only static variables */
 static double   Cycle[] = { 23.0, 28.0, 33.0 };
 static char     cDayOfWeek[] = "MTWTFSS";
 extern LONG     Color[];



 /*  APPPaint() - Parent window WM_PAINT processing routine.
 *
 *   Purpose:
 *       Routine to graph biorhythm cycles and tabulate dates.
 *
 *   Arguments:
 *       hWnd          - Handle of Window owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 *
 *   Globals (static):
 *       Cycle[]       - Array holding period for phy/emot/int: 23,28,33
 *       cDayOfWeek[]  - Array of chars holding first letter of days of week.
 *       Color[]       - Set of colored pens used to identify cycles.
 *
 *   Globals (referenced):
 *       Born          - Birthdate in julian days.  Read from WIN.INI.
 *        SelectDay     - Current day being tracked, day is highlighted.
 *                        This is stored as the number of days from birthdate.
 *                        Initialized to present day in WM_CREATE processing.
 *       Day           - Day number from date born which is top line being
 *                       displayed.  Initially three days before SelectDay.
 *       bBorn         - Boolean indicating whether valid birtdate entered or
 *        rclClient     - Size of client area defined by WM_SIZE message
 *       LinesPerPage  - Number of system font lines on client area, defined
 *                       by WM_SIZE message handling
 *       tmFontInfo    - Text Metric structure defined during WM_CREATE
 *
 *   Description:
 *       Tabulates dates and graphs cycles.  On color displays, weekends
 *       are written in red.  The update rectangle is used to minimize
 *       repaint time of affected client area.
 */
 VOID APIENTRY APPPaint( hWnd )
 HWND   hWnd;
 {
     HPS         hPS;
     POINTL      ptl;
     int         y, i;
     int         start, last;
     char        szDay[16];
     int         Amplitude, offset;
     int         year, month;
     double      day;
     RECTL       rc, rcClip;
     int         DayOfWeek;
     HRGN        hrgnClip;
     POINTL        ptlTextBox[5];

     hPS = WinBeginPaint( hWnd, NULL, &rcClip );

     /* Erase client area */
     WinQueryWindowRect( hWnd, &rc );
     WinFillRect( hPS, &rc, CLR_WHITE );

     /* Label parts of table and graph. */
     ptl.y = rclClient.yTop - tmFontInfo.lMaxBaselineExt + /* Top line */
             tmFontInfo.lMaxDescender;
     ptl.x = 0;
     GpiCharStringAt( hPS, &ptl, 7L, (PCH)"   DATE" );
     ptl.x = cxDateField + tmFontInfo.lAveCharWidth;
     GpiCharStringAt( hPS, &ptl, 3L, (PCH)"LOW" );
     GpiQueryTextBox( hPS, 4L, "HIGH", TXTBOX_COUNT, ptlTextBox );
     ptl.x = rclClient.xRight - ptlTextBox[TXTBOX_CONCAT].x - tmFontInfo.lAveC
     GpiCharStringAt( hPS, &ptl, 4L, (PCH)"HIGH" );

     /* Underline labels from left to right across client area */
     ptl.y = rclClient.yTop - tmFontInfo.lMaxBaselineExt;
     ptl.x = 0;
     GpiMove( hPS, &ptl );
     ptl.x = rclClient.xRight;
     GpiLine( hPS, &ptl );

     /* Draw a vertical line separator between dates and cycles */
     ptl.y = rclClient.yTop;
     ptl.x = cxDateField;
     GpiMove( hPS, &ptl );
     ptl.y = rclClient.yBottom;
     GpiLine( hPS, &ptl );

     /* Draw a dotted vertical center line to reference cycles */
     GpiSetLineType( hPS, LINETYPE_DOT );
     ptl.x = (cxDateField + rclClient.xRight) / 2;
     GpiMove( hPS, &ptl );
     ptl.y = rclClient.yTop;
     GpiLine( hPS, &ptl );
     /* (Should not have to restore line type after EndPaint) */
     GpiSetLineType( hPS, LINETYPE_DEFAULT );

     /* Update only the range of lines which fall into update rectangle */
     start = (int)((rclClient.yTop - rcClip.yTop) / tmFontInfo.lMaxBaselineExt
     if (start<1)
        start = 1;
     last = (int)((rclClient.yTop - rcClip.yBottom) / tmFontInfo.lMaxBaselineE
     if (last>(LinesPerPage-1))
        last = LinesPerPage-1;

     /* Set clip rectangle to completely draw entire rectangle representing
        each date affected.  Start drawing one day before and after
        (outside clip rectangle) so that cycle lines will connect correctly
        with unaffected lines. */
     rcClip.yTop = rclClient.yTop - start*tmFontInfo.lMaxBaselineExt;
     start--;
     last++;
     rcClip.yBottom = rclClient.yTop - last*tmFontInfo.lMaxBaselineExt + 1;
     hrgnClip = GpiCreateRegion( hPS, 1L, &rcClip );
     GpiSetClipRegion( hPS, hrgnClip, &hrgnClip );

     /* List days and date */
     for (y=start; y<=last; y++) {
         /* Get the calendar date from julian day */
         calendar( Born+Day+y-1, &year, &month, &day );
         /* Get offset into days of the week initials array */
         DayOfWeek = (int)((LONG)(Born+Day+y) % 7);
         /* Assemble each of the parts in a buffer */
         sprintf(szDay, " %02d-%02d-%02d",
                 month, (int)day, year - (trunc4((double)year / 100)*100) );
         /* If color available, draw weekends in red */
         if (DayOfWeek > 4)
            GpiSetColor( hPS, CLR_RED );
         ptl.x = 0;
         ptl.y = rclClient.yTop - ((y+1)*tmFontInfo.lMaxBaselineExt -
                 tmFontInfo.lMaxDescender);
         GpiCharStringAt( hPS, &ptl, 1L, (PCH)&cDayOfWeek[DayOfWeek] );
         GpiQueryWidthTable( hPS, (LONG)'W', 1L, &ptl.x );
         GpiCharStringAt( hPS, &ptl, 9L, (PCH)szDay );
         GpiSetColor( hPS, CLR_BLACK );
     }

     /* Amplitude of sin wave is half client area minus space for dates */
     Amplitude = (int)((rclClient.xRight - cxDateField - tmFontInfo.lAveCharWi
     /* Move to right, make room for column of dates */
     offset = (int)(Amplitude + cxDateField + tmFontInfo.lAveCharWidth - (tmFo
     for (i=0; i<3 && bBorn; i++ ) {
         GpiSetColor( hPS, Color[i] );
         for (y=start; y<=last; y++) {
             ptl.x = (int)(sin( (y+Day-1)/Cycle[i]*2*3.14159 ) * Amplitude + o
             ptl.y = rclClient.yTop - (y*tmFontInfo.lMaxBaselineExt +
                         tmFontInfo.lMaxBaselineExt/2);
             if ((y+Day-1 > 0) && (y>start))
                GpiLine( hPS, &ptl );
             else
                GpiMove( hPS, &ptl );
         }
     }

     /* Draw highlight on selected day if visible. */
     if ((SelectDay >= Day) && (SelectDay - Day < LinesPerPage - 1)) {
         rc.xRight = rclClient.xRight;
         rc.xLeft = rclClient.xLeft;
         rc.yTop = rclClient.yTop - (int)(SelectDay - Day + 1) * tmFontInfo.lM
         rc.yBottom = rc.yTop - tmFontInfo.lMaxBaselineExt + 1;
         WinInvertRect( hPS, &rc );
     }

     WinEndPaint( hPS );

     return;
 }


 /*  julian() - Compute julian date from Gregorian calendar date.
 *
 *   Purpose:
 *       Provide a standard time base.
 *
 *   Arguments:
 *       year          - Calendar year
 *       month         - Calendar month
 *       day           - Calendar day and fraction
 *
 *   Return Value:
 *       double        - Julian date converted
 *
 *   Description:
 *       Convert Gregorian dates to Julian Days.  Refer to Alamanac for
 *       Computers (1978), p. B2, Naval Observatory Pub.
 *
 *   Limits:
 *       Valid between ~1900 and 2099.
 *
 */

 double PASCAL julian (year, month, day)
 int    year, month;
 double day;
 {
   double dj;
   double fracDay, intDay;

   fracDay = modf(day, &intDay);
   dj = (long)367*year - 7*(year + (month+9) / 12) / 4 + 275*month / 9 +
        intDay + 1721013.5 + fracDay;
   return dj;
 }


 /*  calendar() - Compute Gregorian calendar date from julian date.
 *
 *   Purpose:
 *       Provide a standard time base.
 *
 *   Arguments:
 *       juldate       - Julian date to convert
 *       year          - Calendar year result
 *       month         - Calendar month result
 *       day           - Calendar day and fraction result
 *
 *   Return Value:
 *       void
 *
 *   Globals (modified):
 *       none
 *
 *   Globals (referenced):
 *       none
 *
 *   Description:
 *       Convert Julian Days to Gregorian date.  Refer to Astronomical
 *       Formulae for Calculators (1979), p. 23, by Jean Meeus.
 *
 *   Limits:
 *       Valid for positive Julian Day values.
 *
 */

 void PASCAL calendar (juldate, year, month, day)
 double juldate;
 int *year;
 int *month;
 double *day;
 {
   long b, c, d, e, z, alf;

   juldate = juldate + 0.5;
   z = trunc4(juldate);
   alf = trunc4((z - 1867216.25)/36524.25);
   b = z + 1 + alf - alf / 4 + 1524;
   c = trunc4((b - 122.1)/365.25);
   d = 365*c + c / 4;
   e = trunc4((b - d)/30.6001);
   *day = b - d - trunc4(30.6001*e) + juldate - z;
   if (e > 13)
       *month = (int)e - 13;
   else
       *month = (int)e - 1;
   if (*month > 2)
       *year = (int)c - 4716;
   else
       *year = (int)c - 4715;
 }

 long PASCAL trunc4( dflValue )
 double dflValue;
 {
    double intValue;
    modf(dflValue, &intValue);
    return (long)intValue;
 }


 BMAP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MSNGR\BMAP.C

 /****************************** MODULE Header ******************************\
 * Module Name:  bmap.c - Messenger application - bitmap module
 *
 * Created: 8/1/89  sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #include "msngr.h"
 #include "string.h"
 #ifdef SLOOP
 #define BITMAPINFOHEADER2 BITMAPINFOHEADER
 #define PBITMAPINFOHEADER2 PBITMAPINFOHEADER
 #define BITMAPINFO2 BITMAPINFO
 #define PBITMAPINFO2 PBITMAPINFO
 #endif

 typedef struct _CDATA {
     RECTL rcl;
     HBITMAP hbm;
     BOOL fSelect;
     BOOL fSelecting;
     NPUSERLIST npUL;
 } CDATA;
 typedef CDATA *NPCDATA;


 typedef struct _PKT {
     HBITMAP hbm;
     SHORT cx;
     SHORT cy;
     char szName[MAX_NAMESTR + 1];
 } PKT;
 typedef PKT *NPPKT;

 extern HWND hwndMsngr;
 extern SHORT cyText;
 extern HAB hab;
 extern HSZ hszAppName;
 extern HSZ hszEmailName;
 extern char szEmailName[];
 extern HWND hwndLB;
 extern ITEMLIST msgTopicItemList[];



 /*
  * local procs
  */
 MRESULT bmpInit(HWND hwnd, NPPKT ppktInit);
 MRESULT sndBmapInit(HWND hwnd, NPUSERLIST pUserItem);
 BOOL sndBmap(NPCDATA pcd);
 HBITMAP SnapRegion(HPS hps, PRECTL prcl);
 void DrawRgn(HPS hps, PRECTL prcl);
 void SortRect(PRECTL prcl, PRECTL prclSorted);
 HDC CreateDC(PSZ lpszDriver, HDC hdcCompat);

 #define max(a,b)    (((a) > (b)) ? (a) : (b))

 /*
  * file globals
  */
 ATOM fmtBmapPkt;
 HPOINTER hptrSelBmap = 0;
 HPOINTER hptrBmap = 0;

 void InitBmapModule()
 {
     fmtBmapPkt = WinAddAtom(WinQuerySystemAtomTable(), SZBMAPDATA);
     hptrSelBmap = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDR_SELBMAP);
     hptrBmap = WinLoadPointer(HWND_DESKTOP, NULL, IDD_GETBITMAP);
 }

 void CloseBmapModule()
 {
     WinDeleteAtom(WinQuerySystemAtomTable(), fmtBmapPkt);
     WinDestroyPointer(hptrSelBmap);
     WinDestroyPointer(hptrBmap);
 }

 HDMGDATA bmpXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     PBYTE pbuf;
     PKT pkt;
     PBITMAPINFO pbmi;
     HDC hdc;
     HPS hpsMem;
     SIZEL size;

     if (pXferInfo->usFmt != fmtBmapPkt)
         return(DDE_NOTPROCESSED);

     if (pXferInfo->usType == XTYP_POKE) {
         /*
          * we have bitmap bits...stick them into pkt.hbm.
          */
         pbuf = DdeAccessData(pXferInfo->hDmgData);

         DdeCopyBlock(pbuf, (PBYTE)&pkt.szName[0], MAX_NAMESTR + 1L);
         pbmi = (PBITMAPINFO)(pbuf + MAX_NAMESTR + 1);

         pkt.cx = pbmi->cx;
         pkt.cy = pbmi->cy;
         size.cx = (LONG)pkt.cx;
         size.cy = (LONG)pkt.cy;
         hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);
         hpsMem = GpiCreatePS(hab, hdc, &size,
                 PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC );
         pkt.hbm = GpiCreateBitmap(hpsMem, (PBITMAPINFOHEADER2)pbmi, CBM_INIT,
                 (PBYTE)&pbmi->argbColor[1 << pbmi->cBitCount],
                 (PBITMAPINFO2)pbmi);
         GpiAssociate(hpsMem, NULL);
         GpiDestroyPS(hpsMem);
         DevCloseDC(hdc);
         DdeFreeData(pXferInfo->hDmgData);

         WinLoadDlg(HWND_DESKTOP, hwndMsngr, BmpDlgProc, 0L, IDD_GETBITMAP,
                 (PVOID)&pkt);
         return(1);
     }
     return(0);
 }


 /*
  * This is the proc used for receiving a bitmap
  */
 MRESULT EXPENTRY BmpDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     HBITMAP hbm;
     HPS hps;
     WRECT wrc;

     switch(msg) {
     case WM_INITDLG:
         return(bmpInit(hwnd, (NPPKT)(SHORT)mp2));
         break;

     case WM_DESTROY:
         if (hbm = ((NPPKT)WinQueryWindowUShort(hwnd, QWS_USER))->hbm)
             GpiDeleteBitmap(hbm);
         WinFreeMem(hheap, (NPBYTE)WinQueryWindowUShort(hwnd, QWS_USER),
                 sizeof(PKT));
         break;

     case WM_WINDOWPOSCHANGED:
         /*
          * hide the OK button when minimized since it messes up the icon.
          */
         if ((LONG)mp2 & AWP_MINIMIZED)
             WinShowWindow(WinWindowFromID(hwnd, MBID_OK), FALSE);
         else if ((LONG)mp2 & AWP_RESTORED)
             WinShowWindow(WinWindowFromID(hwnd, MBID_OK), TRUE);
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_COMMAND:
         WinDestroyWindow(hwnd);
         break;

     case WM_PAINT:
         WinDefDlgProc(hwnd, msg, mp1, mp2);
         /*
          * draw the bitmap just above the OK button.
          */
         hps = WinGetPS(hwnd);
         WinQueryWindowRect(WinWindowFromID(hwnd, MBID_OK), (PRECTL)&wrc);
         WinMapWindowPoints(WinWindowFromID(hwnd, MBID_OK), hwnd, (PPOINTL)&wr
         wrc.yBottom = wrc.yTop + cyText / 2;
         hbm = ((NPPKT)WinQueryWindowUShort(hwnd, QWS_USER))->hbm;
         WinDrawBitmap(hps, hbm, (PRECTL)NULL, (PPOINTL)&wrc, 0L, 0L, DBM_NORM
         WinReleasePS(hps);
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
     }
     return(0);
 }


 MRESULT bmpInit(hwnd, ppktInit)
 HWND hwnd;
 NPPKT ppktInit;
 {
     char szTitle[MAX_TITLESTR];
     WRECT wrc;
     NPPKT ppkt;
     SHORT cxMin;

     if (!(ppkt = (NPPKT)WinAllocMem(hheap, sizeof(PKT))))
         return(1);
     *ppkt = *ppktInit;
     WinSetWindowUShort(hwnd, QWL_USER, (USHORT)ppkt);
     /*
      * This is required because currently, automatic ICON resource loading
      * is not supported for dialogs.
      */
     WinSendMsg(hwnd, WM_SETICON, (MPARAM)hptrBmap, 0L);
     /*
      * Set up title.
      */
     WinQueryWindowText(hwnd, MAX_TITLESTR, (PSZ)szTitle);
     lstrcat(szTitle, szTitle, ppkt->szName);
     WinSetWindowText(hwnd, (PSZ)szTitle);
     /*
      * resize the dialog so the bitmap just fits.
      */
     WinQueryWindowRect(hwnd, (PRECTL)&wrc);
     cxMin = wrc.xRight;
     WinQueryWindowRect(WinWindowFromID(hwnd, MBID_OK), (PRECTL)&wrc);
     WinMapWindowPoints(WinWindowFromID(hwnd, MBID_OK), hwnd, (PPOINTL)&wrc, 2
     WinSetWindowPos(hwnd, NULL, 0, 0, max(wrc.xLeft * 2 + ppkt->cx, cxMin),
             wrc.yTop + ppkt->cy + cyText +
             (SHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYTITLEBAR), SWP_SIZE);
     return(0);
 }



 MRESULT EXPENTRY SendBitmapDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     NPCDATA pcd;
     POINTL pt;
     HPS hps;

     pcd = (NPCDATA)WinQueryWindowUShort(hwnd, QWS_USER);

     switch (msg) {
     case WM_INITDLG:
         return(sndBmapInit(hwnd, (NPUSERLIST)(SHORT)mp2));
         break;

     case WM_DESTROY:
         WinFreeMem(hheap, (NPBYTE)pcd, sizeof(CDATA));
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDC_SENDBITMAP:
             if (sndBmap(pcd))
                 WinDismissDlg(hwnd, 0);
             break;

         case MBID_CANCEL:
             WinDismissDlg(hwnd, 0);
             break;

         case IDC_SELECT:
             pcd->fSelect = TRUE;
             WinSetCapture(HWND_DESKTOP, hwnd);
             break;
         }
         break;

     case WM_BUTTON1DOWN:
         if (pcd->fSelect) {
             if (pcd->hbm) {
                 GpiDeleteBitmap(pcd->hbm);
                 pcd->hbm = NULL;
             }
             WinSetRect(hab, &pcd->rcl, SHORT1FROMMP(mp1), SHORT2FROMMP(mp1),
                     SHORT1FROMMP(mp1), SHORT2FROMMP(mp1));
             WinMapWindowPoints(hwnd, (HWND)HWND_DESKTOP, (PPOINTL)&pcd->rcl,
             hps = WinGetScreenPS(HWND_DESKTOP);
             DrawRgn(hps, &pcd->rcl);
             WinReleasePS(hps);
             pcd->fSelecting = TRUE;
         }
         break;

     case WM_MOUSEMOVE:
         if (pcd->fSelect) {
             WinSetPointer(HWND_DESKTOP, hptrSelBmap);
         } else {
             WinSetPointer(HWND_DESKTOP,
                     WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, FALSE));
         }
         if (pcd->fSelecting) {
             hps = WinGetScreenPS(HWND_DESKTOP);
             DrawRgn(hps, &pcd->rcl);    /* erase old rect */
             pt.x = SHORT1FROMMP(mp1);
             pt.y = SHORT2FROMMP(mp1);
             WinMapWindowPoints(hwnd, HWND_DESKTOP, &pt, 1);
             pcd->rcl.xRight = pt.x;
             pcd->rcl.yTop = pt.y;
             DrawRgn(hps, &pcd->rcl);    /* draw new one */
             WinReleasePS(hps);
         }
         break;

     case WM_BUTTON1UP:
         if (pcd->fSelecting) {
             WinSetCapture(HWND_DESKTOP, (HWND)NULL);
             hps = WinGetScreenPS(HWND_DESKTOP);
             DrawRgn(hps, &pcd->rcl);
             pcd->hbm = SnapRegion(hps, &pcd->rcl);
             WinReleasePS(hps);
             pcd->fSelecting = FALSE;
             pcd->fSelect = FALSE;
             WinEnableWindow(WinWindowFromID(hwnd, IDC_SENDBITMAP),
                     !WinIsRectEmpty(hab, &pcd->rcl));
         }
         break;


     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }


 /*
  * returns fFailed
  */
 MPARAM sndBmapInit(hwnd, pUserItem)
 HWND hwnd;
 NPUSERLIST pUserItem;
 {
     NPCDATA pcd;
     char szTitle[MAX_TITLESTR];
     char szName[MAX_NAMESTR];

     if (!(pcd = (NPCDATA)WinAllocMem(hheap, sizeof(CDATA))))
         return(1);
     WinSetRectEmpty(hab, &pcd->rcl);
     pcd->hbm = NULL;
     pcd->fSelect = FALSE;
     pcd->fSelecting = FALSE;
     pcd->npUL = pUserItem;
     if (pcd->npUL->hConvMsg == NULL) {
         NotifyUser(SZCANTCONNECT);
         return(1);
     }
     WinQueryWindowText(hwnd, MAX_TITLESTR, szTitle);
     DdeGetHszString(pcd->npUL->hsz, szName, (LONG)MAX_NAMESTR);
     lstrcat(szTitle, szTitle, szName);
     WinSetWindowText(hwnd, (PSZ)szTitle);
     WinSetWindowUShort(hwnd, QWS_USER, (USHORT)pcd);
     return(0);
 }


 BOOL sndBmap(pcd)
 NPCDATA pcd;
 {
     BITMAPINFOHEADER bih;
     SHORT cbBuffer, cbBitmapInfo;
     PBYTE pbBuffer;
     PBITMAPINFO pbmi;
     PSZ pszName;
     SEL sel;
     HPS hps;
     HDC hdc;
     SIZEL size;

     /*
      * Compute the size of the image-data buffer and the bitmap information
      * structure.
      */
     GpiQueryBitmapParameters(pcd->hbm, &bih);
     cbBuffer = (((bih.cBitCount * bih.cx) + 31) / 32) * 4;
     if (cbBuffer > 0xFFFF / bih.cy / bih.cPlanes) {
         NotifyUser(SZTOOBIG);
         return(FALSE);
     }
     cbBuffer *= bih.cy * bih.cPlanes;
     cbBitmapInfo = sizeof(BITMAPINFO) +
         (sizeof(RGB) * (1 << bih.cBitCount));

     /*
      * Allocate memory for the image data-buffer and the bitmap information
      * structure.
      */
     DosAllocSeg(cbBuffer + cbBitmapInfo + MAX_NAMESTR + 1, &sel, 0);
     pszName = (PSZ)MAKEP(sel, 0);
     lstrcpy(pszName, szEmailName);
     pbmi = (PBITMAPINFO)(pszName + MAX_NAMESTR + 1);
     pbBuffer = (PBYTE)&pbmi->argbColor[1 << bih.cBitCount];
     *(PBITMAPINFOHEADER)pbmi = bih;

     size.cx = (LONG)bih.cx;
     size.cy = (LONG)bih.cy;
     hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);
     hps = GpiCreatePS(hab, hdc, &size,
             PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC );
     GpiSetBitmap(hps, pcd->hbm);
     GpiQueryBitmapBits(hps, 0L, (LONG)bih.cy, (PBYTE)pbBuffer,
             (PBITMAPINFO2)pbmi);
     GpiAssociate(hps, NULL);
     GpiDestroyPS(hps);
     DevCloseDC(hdc);

     if (!DdeClientXfer(pszName,
             (LONG)(cbBuffer + cbBitmapInfo + MAX_NAMESTR + 1),
             pcd->npUL->hConvMsg,
             msgTopicItemList[IIL_BMPXFER].hszItem,
             fmtBmapPkt, XTYP_POKE, ulTimeout, 0L)) {
         MyPostError(DdeGetLastError());
     }

     DosFreeSeg(sel);
     GpiDeleteBitmap(pcd->hbm);
     pcd->hbm = NULL;
     WinSetRectEmpty(hab, &pcd->rcl);
     return(TRUE);
 }


 HBITMAP SnapRegion(hps, prcl)
 HPS hps;
 PRECTL prcl;
 {
     HDC hdc;
     HBITMAP hbm, hbmOld;
     BITMAPINFOHEADER bih;
     POINTL rgpt[3];
     HPS hpsMem;
     SIZEL size;

     SortRect(prcl, prcl);
     WinInflateRect(hab, prcl, -1, -1);

     size.cx = (USHORT)(prcl->xRight - prcl->xLeft);
     size.cy = (USHORT)(prcl->yTop - prcl->yBottom);

     /* Create a memory DC */
     hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);

     /* create a memory PS */
     hpsMem = GpiCreatePS(hab, hdc, &size,
             PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC );

     /* Create a bitmap */
     bih.cbFix = sizeof(BITMAPINFOHEADER);
     bih.cx = (SHORT)size.cx;
     bih.cy = (SHORT)size.cy;
     bih.cPlanes = 1;
     bih.cBitCount = 8;
     hbm = GpiCreateBitmap(hpsMem, (PBITMAPINFOHEADER2)&bih, 0L, 0, 0);
     if (hbm == GPI_ERROR)
         return(0);

     /* put the bitmap into the memory PS */
     hbmOld = GpiSetBitmap(hpsMem, hbm);

     /* copy the window to the memory PS */
     rgpt[0].x = 0;
     rgpt[0].y = 0;
     rgpt[1].x = size.cx;
     rgpt[1].y = size.cy;
     rgpt[2].x = prcl->xLeft;
     rgpt[2].y = prcl->yBottom;
     GpiBitBlt(hpsMem, hps, 3L, (PPOINTL)&rgpt[0], ROP_SRCCOPY, 0L);

     /* free the bitmap */
     GpiSetBitmap(hpsMem, hbmOld);

     /* destroy the memory DC */
     GpiAssociate(hpsMem, NULL);
     GpiDestroyPS(hpsMem);
     DevCloseDC(hdc);
     return(hbm);
 } /* end snapregion */


 HDC CreateDC(lpszDriver, hdcCompat)
 PSZ  lpszDriver;
 HDC hdcCompat;
 {
     struct {
         ULONG FAR *lpLogAddr;
         PSZ  lpszDriver;
     } opendc;

     opendc.lpLogAddr = NULL;
     opendc.lpszDriver = lpszDriver;

     return((HDC)DevOpenDC(hab, OD_MEMORY, (PSZ)"*", 2L,
             (PDEVOPENDATA)&opendc, hdcCompat));
 }


 void DrawRgn(hps, prcl)
 HPS hps;
 PRECTL prcl;
 {
     RECTL rclSorted;

     SortRect(prcl, &rclSorted);
     WinDrawBorder(hps, &rclSorted, 1, 1, SYSCLR_WINDOW, SYSCLR_WINDOW,
             DB_DESTINVERT | DB_STANDARD);
 }

 void SortRect(prcl, prclSorted)
 PRECTL prcl;
 PRECTL prclSorted;
 {
     LONG l;

     WinCopyRect(hab, prclSorted, prcl);
     if (prclSorted->yTop < prclSorted->yBottom) {
         l = prclSorted->yBottom;
         prclSorted->yBottom = prclSorted->yTop;
         prclSorted->yTop = l;
     }

     if (prclSorted->xRight < prclSorted-> xLeft) {
         l = prclSorted->xRight;
         prclSorted->xRight = prclSorted->xLeft;
         prclSorted->xLeft = l;
     }
 }






 BROWSE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BROWSE\AVBROWSE\BROWSE.C

 /*
     browse.c -- AVIO File Browsing Utility

     Created by Microsoft Corporation, 1989
 */
 #define        INCL_WINTRACKRECT
 #define        INCL_WINWINDOWMGR
 #define        INCL_WINPOINTERS
 #define INCL_WINFRAMEMGR
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "avio.h"
 #include "browse.h"
 #include <opendlg.h>
 /*
     Constants
 */
 #define MAXLINELEN        120
 #define AVIO_PS_ROWS        25
 #define        AVIO_PS_COLS        80
 /*
     Global Variables
 */
 FILE        *pfInput;
 PFNWP        pfnOldClient;
 char        *aszLines[NUM_DATA_LINES];
 SHORT        sTopLine = 0;
 DLF        dlfInput;
 HFILE        hfInput;
 USHORT        usAction;
 LBINFO        lbiData;
 HPOINTER hptrWait;
 HPOINTER hptrArrow;
 HWND        hWndClient;
 HWND        hWndFrame;
 BOOL        fLargeFont = FALSE;
 SHORT        sMaxLine;
 /*
     Open the input file
 */
 int cdecl main(int argc, char *argv[]) {
      static CHAR szClientClass[] = "Browse";
      static CHAR szCaption[]         = "";
      HAB        hAB;
      HMQ        hmq;
      QMSG        qmsg;
      ULONG        flFrameFlags = FCF_STANDARD | FCF_HORZSCROLL | FCF_VERTSCRO
      ULONG         flFrameStyle = WS_VISIBLE | FS_SCREENALIGN;
      char        *szInFile;

      hAB = WinInitialize(0);
      hmq = WinCreateMsgQueue(hAB, 0);

      WinRegisterClass(hAB, szClientClass, BrowseWndProc, CS_SYNCPAINT, 0);

      hWndFrame = WinCreateStdWindow(HWND_DESKTOP, flFrameStyle,
                                     &flFrameFlags, szClientClass, szCaption,
                                      0L, (HMODULE) NULL, ID_RESOURCE, &hWndCl
      /*
         Get the hourglass and arrow pointers
      */
      hptrWait  = WinQuerySysPointer(HWND_DESKTOP, SPTR_WAIT,  FALSE);
      hptrArrow = WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, FALSE);

      if (argc == 1) pfInput = stdin;
      else {
         if (!(pfInput = fopen(argv[1], "r"))) {
             fprintf(stderr, "***Error:  Could not open %s", szInFile);
             return(-1);
         }
      }
      ReadFile();
      /*
         Setup AVIO PS and force a paint
         Note:  This subclasses the client and frame windows
      */
      lbiData.sPSrows        = AVIO_PS_ROWS;
      lbiData.sPScols        = AVIO_PS_COLS;
      lbiData.sRows        = sTopLine;
      lbiData.sCols        = sMaxLine;
      lbiData.pfnQL        = (PFNQL) RetrieveLine;
      lbiData.fLargeFont        = FALSE;
      AvioInit(&lbiData);
      /*
         Process messages
      */
      while (WinGetMsg(hAB, &qmsg, NULL, 0, 0)) WinDispatchMsg(hAB, &qmsg);

      /* Blast the AVIO PS */
      AvioClose();

      WinDestroyWindow(hWndFrame);
      WinDestroyMsgQueue(hmq);
      WinTerminate(hAB);
      return 0;
 }

 void ReadFile(void) {
 /*
     Reads in a file using <stdio.h> fgets() calls.
     It might be wise to put better word wrap facilities here
 */
     char        szLine[MAXLINELEN];

     /* Put up the hourglass */
     WinSetPointer(HWND_DESKTOP, hptrWait);

     /* Reinitialize buffer, MaxLineLength */
     for (; sTopLine > 0; ) free(aszLines[--sTopLine]);
     sMaxLine = 0;

     /* Read in the file */
     while (fgets(szLine, MAXLINELEN, pfInput)) {

         /* Convert LF (\n) into NULL (\0) */
         if (szLine[strlen(szLine) - 1] == '\n') {
             szLine[strlen(szLine) - 1] = 0;
         } else szLine[MAXLINELEN - 1] = 0;

         if (StoreLine(szLine)) {
             fprintf(stderr,"***Error:  Line buffer full\n");
             return;
         }
     }
     fclose(pfInput);

     /* Reset the mouse pointer */
     WinSetPointer(HWND_DESKTOP, hptrArrow);

     return;
 }

 SHORT StoreLine(char *szLine) {
 /*
     Put a line into the line buffer; line numbers are free
     For > 64K data, add code here and in RetrieveLine
 */
     int                i, cLinePos;
     BOOL        fDone;
     /*
         Check if top line exceeded, or malloc() fails
     */
     if (sTopLine == NUM_DATA_LINES)  return -1;
     /*
         Compute line length with tabs expanded
     */
     cLinePos = 0;
     for (i = 0; i < MAXLINELEN; i++) {
         switch(szLine[i]) {
             case '\0':
                 cLinePos++; i = MAXLINELEN;
                 break;
             case '\t':
                 do {
                     cLinePos++;
                 } while (cLinePos % 8);
                 break;

             default:
                 cLinePos++;
         }

     }
     if (cLinePos > sMaxLine) sMaxLine = cLinePos;
     if (!(aszLines[sTopLine] = malloc(cLinePos))) return -1;
     /*
         Copy szLine into the line buffer.  Expand tabs here.
     */
     i = cLinePos = 0; fDone = FALSE;
     while ((i <= MAXLINELEN) && (!fDone)) {
         switch(szLine[i]) {
             case '\t':
                 do {
                     aszLines[sTopLine][cLinePos++] = ' ';
                 } while (cLinePos % 8);
                 break;

             default:
                 aszLines[sTopLine][cLinePos++] = szLine[i];
                 fDone = !szLine[i];
                 break;
         }
         i++;
     }
     sTopLine++;
     return 0;
 }

 char * _loadds RetrieveLine(USHORT usLineNum) {
 /*
     Return line numbered usLineNum
 */
     if ((SHORT) usLineNum >= sTopLine) {                /* Out of range */
         return NULL;
     }
     return aszLines[usLineNum];
 }

 MRESULT CALLBACK BrowseWndProc(hWnd, msg, mp1, mp2)
 HWND hWnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
 /*
     Handle the About... and Open... messages
 */
     switch(msg) {
         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {
                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hWnd, AboutDlgProc,
                               (HMODULE) NULL, IDD_ABOUT, NULL);
                     return 0;

                 case IDM_OPEN:
                     /*
                         Open the file, using the file dialog
                         then reopen it with stdio calls
                     */
                     SetupDLF(&dlfInput, DLG_OPENDLG, &hfInput,
                         "\\*.*", NULL, "Browse Open File",
                         "Select a file to be browsed.");
                     DlgFile(hWnd, &dlfInput);
                     pfInput = fopen(dlfInput.szOpenFile, "r");
                     ReadFile();
                     /*
                         Close the opened handle
                     */
                     DosClose(hfInput);

                     /* Fix up the screen display */
                     lbiData.sRows = sTopLine;
                     lbiData.sCols = sMaxLine;
                     lbiData.fLargeFont = fLargeFont;
                     AvioInit(&lbiData);

                     return 0;

                 case IDM_FONT:
                     AvioLargeFont(fLargeFont = !fLargeFont);
                        return 0;

                 default: return 0;
             }
             break;
         default: return WinDefWindowProc(hWnd, msg, mp1, mp2);
     }
     return 0L;
 }

 MRESULT CALLBACK AboutDlgProc(hDlg, msg, mp1, mp2)
 /*
     About... dialog procedure
 */
 HWND        hDlg;
 USHORT        msg;
 MPARAM        mp1;
 MPARAM        mp2;
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 BROWSE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BROWSE\VBROWSE\BROWSE.C

 /*
     VIO File Browsing Application
     Created by Microsoft Corporation, 1989
 */
 #define         INCL_KBD
 #define         INCL_VIO
 #include <stdio.h>
 #include <stdlib.h>
 #include <os2.h>
 #include <string.h>
 #include "browse.h"
 /*
     Global Variables
 */
 FILE        *pfInput;
 char        *aszLines[NUM_DATA_LINES];
 SHORT        sTopLine= -1;
 SHORT        sRows;
 SHORT        HorScrollPos=0;
 BYTE        abBlank[2] = { 0x20, 0x07 };

 /*
     Macros for Vio calls
     The last parameter is zero because we're using a VIO PS
 */
 #define ClearScreen()        VioScrollDn(0, 0, -1, -1, -1, abBlank, 0)
 #define        Move(r,c)        VioSetCurPos(r, c, 0)
 #define ScrollDown(n)        VioScrollDn(0, 0, -1, -1,  n, abBlank, 0)
 #define ScrollUp(n)        VioScrollUp(0, 0, -1, -1,  n, abBlank, 0)
 #define Write(s)        VioWrtTTY(s, strlen(s), 0)
 /*
     Macros for bounds checking
 */
 #define Abs(x)                (((x) > 0) ? (x) : (-(x)))
 #define Max(x, y)        (((x) > (y)) ? (x) : (y))
 #define Min(x, y)        (((x) < (y)) ? (x) : (y))
 #define LowerBound(pos, disp, lbound)        Max(pos - disp, lbound)
 #define UpperBound(pos, disp, ubound)        Min(pos + disp, ubound)

 /*
     Functions
 */
 int cdecl main(int argc, char *argv[]) {
 /*
     Open the input file and initialize globals
 */
     char        *szFilename;
     VIOMODEINFO        viomiMode;

     /*
         Open the Input File
     */
     if (argc == 1)
         pfInput = stdin;
     else {
         szFilename = argv[1];
         if (!(pfInput = fopen(szFilename,"r"))) {
             fprintf(stderr, "***Error:  Could not open %s", szFilename);
             return(-1);
         }
     }
     /*
         Read it into the line buffer
     */
     if (ReadFile()) return(-1);
     /*
         Get the video parameters
     */
     viomiMode.cb = sizeof(viomiMode);
     VioGetMode(&viomiMode, 0);
     sRows = (SHORT) viomiMode.row;

     DisplayScreen(0, TRUE);
     ManipulateFile();

     return 0;
 }

 SHORT ReadFile(VOID) {
 /*
     Read lines from the file into the line buffer
     If there's an error, abort the program (return -1)
 */
     char szLine[MAXLINELENGTH];

     while (fgets(szLine, MAXLINELENGTH, pfInput)) {

         /* Convert LF (\n) character to NULL (\0) */
         if (szLine[strlen(szLine)-1] == '\n')
             szLine[strlen(szLine)-1] = 0;
         else {
             fprintf(stderr,"***Error:  Incomplete line read\n");
             return(-1);
         }

         /* Put the line into the line buffer */
         if (StoreLine(szLine)) {
             fprintf(stderr,"***Error:  Line buffer full\n");
             return(-1);
         }
     }

     /* Close the Input file */
     fclose(pfInput);
     return 0;
 }

 VOID ManipulateFile(VOID) {
 /*
     Main loop for display processing
 */
     CHAR    ch;
     SHORT   sLine = 0;

     /* The main command loop */
     while ((ch = GetKbdInput()) != ESC) {
         /*
             Take user input and compute new top line of screen
             by taking appropriate jump in jumptable.

             Note:  no horizontal scrolling.
         */
         switch (ch) {
         case LINE_UP:         sLine = LowerBound(sLine, 1, 0);
         case LINE_DOWN:  sLine = UpperBound(sLine, 1, BOTTOM);
         case PAGE_UP:         sLine = LowerBound(sLine, sRows, 0);
         case PAGE_DOWN:  sLine = UpperBound(sLine, sRows, BOTTOM);        bre
         case HOME_KEY:         sLine = 0;
         case END_KEY:         sLine = BOTTOM;
         default:                                                        break
         }
         DisplayScreen((USHORT) sLine, !ch);
     }

     /* Set Cursor to the bottom of the screen */
     Move((USHORT) sRows - 1, 0);
 }

 SHORT StoreLine(char *szLine) {
 /*
     Put a line into the line buffer; line numbers are free
     For > 64K data, add code here and in RetrieveLine
 */
     /*
         Check if top line exceeded, or if malloc() fails
     */
     if ((sTopLine == NUM_DATA_LINES) ||
         ((aszLines[++sTopLine] = malloc(strlen(szLine) + 1)) == NULL))

         return -1;
     /*
         Copy szLine into the line buffer
     */
     strcpy(aszLines[sTopLine], szLine);
     return 0;
 }

 SHORT RetrieveLine(char **pszLine , USHORT usLineNum) {
 /*
     Return line numbered usLineNum
 */
     if ((SHORT) usLineNum > sTopLine) return -1;  /* Out of range */
     *pszLine = aszLines[usLineNum];
     return 0;
 }

 VOID DisplayScreen(USHORT usDisplayTop, BOOL fForceDraw) {
 /*
     Display lines on the screen, starting at usDisplayTop
     by scrolling, then painting new information
 */
     SHORT            sDelta;
     static USHORT   usOldDispTop;

     sDelta = usDisplayTop - usOldDispTop;
     /*
         If only a few lines need repainting...
     */
     if ((Abs(sDelta) < sRows) && !fForceDraw ) {
         /*
             Moving to a "higher line", so:
                 Scroll down by the amount (make the difference positive)
                 Paint in the lines at the top
         */
         if (sDelta < 0) {
             ScrollDown(-sDelta);
             Refresh(usDisplayTop, -sDelta, 0);
         } else {
             /*
                 Moving to a "lower line", so:
                 Scroll the information up, and paint at the bottom
             */
             ScrollUp(sDelta);
             Refresh(usDisplayTop + sRows - sDelta, sDelta, sRows - sDelta);
         }
     } else {        /* Paint the entire screen */
         ClearScreen();
         Refresh(usDisplayTop, sRows, 0);
     }
     usOldDispTop = usDisplayTop;
 }

 VOID Refresh (USHORT iLine, USHORT usLines, USHORT usStart) {
 /*
     Updates usLines lines, starting at line iLine in the line
     buffer, and line usStart on the screen
 */
     USHORT usLine;
     char   *szLine;

     for (usLine = 0; usLine < usLines; usLine++) {
         /*
             Read the line, set the cursor, print the line
         */
         if (RetrieveLine(&szLine, (iLine + usLine))) break;
         Move((usStart + usLine), 0);
         Write(szLine);
     }
 }

 CHAR GetKbdInput(VOID) {
 /*
     Get chars, then check scan codes and return our own values
 */
     KBDKEYINFO kbciKeyInfo;

     /*
         Wait for characters
     */
     KbdCharIn(&kbciKeyInfo, IO_WAIT, 0);

     switch (kbciKeyInfo.chScan) {
         case ESC:                         /* escape */
         case LINE_UP:
         case LINE_DOWN:
         case PAGE_UP:
         case PAGE_DOWN:
         case HOME_KEY:
         case END_KEY:
             return kbciKeyInfo.chScan; break;
         default:
            return((CHAR) NULL); break;
     }
 }


 CALC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CALC\CALC.C

 /****************************** Module Header *******************************
 /*
 /* Module Name:  calc.c - Calc application
 /*
 /* OS/2 Presentation Manager version of Calc, ported from Windows version
 /*
 /* Created by Microsoft Corporation, 1987
 /*
 /****************************************************************************

 #define INCL_WININPUT
 #define INCL_WINPOINTERS
 #define INCL_WINMENUS
 #define INCL_WINSYS
 #define INCL_WINCLIPBOARD
 #define INCL_GPIPRIMITIVES
 #define INCL_GPIBITMAPS
 #define INCL_GPILCIDS
 #define INCL_DEV
 #define INCL_ERRORS
 #define INCL_DOSPROCESS
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSNLS
 #include <os2.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include "calc.h"

 /****************************************************************************
 /*
 /*  GLOBAL VARIABLES
 /*
 /****************************************************************************

 CHAR  chLastKey, chCurrKey;
 CHAR  szreg1[20], szreg2[20], szmem[20], szregx[20];
 CHAR  szTitle[30], szErrorString[20], szPlusMinus[2];
 SHORT sCharWidth, sCharHeight;
 extern BOOL fError;
 BOOL  fValueInMemory = FALSE;
 BOOL  fMDown = FALSE;                       /* TRUE iff 'm' key depressed  */
 UCHAR uchMScan = 0;                       /* scan code for 'm' key       */

 #define TOLOWER(x)   ( (((x) >= 'A') && ((x) <= 'Z')) ? (x)|0x20 : (x))
 #define WIDTHCONST  28
 #define CXCHARS     37
 #define CYCHARS     13

 HAB hab;
 HDC hdcLocal;                            /* Local used for button bitmap */
 HPS hpsLocal;
 HDC hdcSqr;                            /* Sqr used for square-root bitmap */
 HPS hpsSqr;
 HBITMAP hbmLocal, hbmSqr;
 HMQ  hmqCalc;
 HWND hwndCalc, hwndMenu;
 HWND hwndCalcFrame;
 HPS  hpsCalc;
 HDC  hdcCalc;
 HPOINTER hptrFinger;

 DEVOPENSTRUC dop =                    /* used by DevOpenDC */
 {
     NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL
 };

 static char achKeys[25] =               /* keyboard keys */
 {
     '\271', '0', '.', '\261', '+', '=',
     '\272', '1', '2', '3', '-', 'c',
     '\273', '4', '5', '6', '*', '%',
     '\274', '7', '8', '9', '/', 'q',
     NULL
 };

 static CHAR achDKeys[25] =    /* 4th key is plusminus */
 {
     ' ', '0', '.', '+', '+', '=',
     ' ', '1', '2', '3', '-', 'C',
     ' ', '4', '5', '6', '*', '%',
     ' ', '7', '8', '9', '/', ' ',
     NULL
 };

 /****************************************************************************
 /*
 /*  PROCEDURE DECLARATIONS
 /*
 /****************************************************************************

 VOID FarStrcpy( PSZ, PSZ);
 MPARAM EXPENTRY AboutDlgProc( HWND, USHORT, MPARAM, MPARAM);
 BOOL CalcInit(VOID);
 VOID CalcPaint( HWND, HPS);
 VOID CalcTextOut( HPS, INT, INT, PCH, INT);
 MRESULT EXPENTRY CalcWndProc( HWND, USHORT, MPARAM, MPARAM);
 VOID cdecl main(VOID);
 VOID DataXCopy( VOID);
 VOID DataXPaste( VOID);
 VOID DrawNumbers( HPS);
 VOID Evaluate(BYTE);
 BOOL FlashSqr( HPS, PWPOINT);
 VOID FlipKey( HPS, INT, INT);
 VOID FrameKey( HPS, INT, INT);
 VOID InitCalc( VOID);
 BOOL InterpretChar( CHAR);
 VOID ProcessKey( PWPOINT);
 BOOL PSInit( VOID);
 CHAR Translate( PWPOINT);
 VOID UpdateDisplay( VOID);


 /****************************************************************************
 /****************************************************************************
 VOID CalcTextOut( hps, iX, iY, pch, iCount)

 HPS hps;
 INT iX, iY;
 PCH pch;
 INT iCount;
 {
     POINTL ptl;

     ptl.x = iX;
     ptl.y = iY;

     GpiSetColor( hps, CLR_BLACK);
     GpiCharStringAt( hps, (PPOINTL)&ptl, (LONG)iCount, (PSZ)pch);
 }


 /****************************************************************************
 /* Write the appropriate number or error string to the display area
 /* and mark memory-in-use if appropriate.
 /****************************************************************************
 VOID
 UpdateDisplay()
 {
     RECTL rcl;

     rcl.xLeft = (6 * sCharWidth);
     rcl.yBottom = 1050 * sCharHeight / 100;
     rcl.xRight = rcl.xLeft + (12 * sCharWidth);
     rcl.yTop = rcl.yBottom + (3 * sCharHeight) / 2;

     WinFillRect( hpsCalc, &rcl, CLR_WHITE);         /* paint display area whi
     if( fError)
         WinDrawText( hpsCalc
                    , -1
                    , szErrorString
                    , &rcl
                    , CLR_BLACK
                    , CLR_WHITE
                    , DT_RIGHT | DT_VCENTER );
     else
         WinDrawText( hpsCalc
                    , -1
                    , szreg1
                    , &rcl
                    , CLR_BLACK
                    , CLR_WHITE
                    , DT_RIGHT | DT_VCENTER );

     if (fValueInMemory)                 /* little black square shows mem use
     {
         rcl.xLeft = (6 * sCharWidth);
         rcl.yBottom = 1050 * sCharHeight / 100;
         rcl.xRight = rcl.xLeft + (sCharWidth / 2);
         rcl.yTop = rcl.yBottom + (sCharHeight / 2);
         WinFillRect( hpsCalc, &rcl, CLR_BLACK);
     }
 }


 /****************************************************************************
 /*  Display helpful info
 /****************************************************************************
 MPARAM EXPENTRY
 AboutDlgProc( hwnd, msg, mp1, mp2)

 HWND   hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     if (msg == WM_COMMAND)
     {
         WinDismissDlg(hwnd, TRUE);
         return(MPFROMSHORT(TRUE));
     }
     else return(WinDefDlgProc( hwnd, msg, mp1, mp2));
 }


 /****************************************************************************
 /*  General initialization
 /****************************************************************************
 BOOL
 CalcInit()
 {
     hab = WinInitialize( NULL);

     hmqCalc = WinCreateMsgQueue( hab, 0);
     if( !hmqCalc)
         return(FALSE);

     WinLoadString( NULL, NULL, 1, 30, (PSZ)szTitle);
     WinLoadString( NULL, NULL, 2, 20, (PSZ)szErrorString);
     WinLoadString( NULL, NULL, 3, 2, (PSZ)szPlusMinus);

     if (!WinRegisterClass( hab, szTitle, CalcWndProc, CS_SIZEREDRAW, 0))
         return(FALSE);

     hptrFinger = WinLoadPointer( HWND_DESKTOP, (HMODULE)NULL, IDP_FINGER);

     InitCalc();                         /* arithmetic initialization */

     return(TRUE);
 }

 /****************************************************************************
 /*  main procedure
 /****************************************************************************
 VOID cdecl
 main()
 {
     QMSG  qmsg;
     ULONG ulFCF;

     if (!CalcInit()) {                            /* general initialization *
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         goto exit;
     }

     if (!PSInit()) {                            /* presentation spaces & bitm
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         goto exit;
     }

     ulFCF = FCF_STANDARD & ~(LONG)(FCF_SIZEBORDER | FCF_MAXBUTTON);
     hwndCalcFrame = WinCreateStdWindow( HWND_DESKTOP
                                       , WS_VISIBLE | FS_BORDER
                                       , &ulFCF
                                       , szTitle
                                       , NULL
                                       , 0L
                                       , NULL
                                       , IDR_CALC
                                       , &hwndCalc);

     WinSetWindowPos( hwndCalcFrame
                    , (HWND)NULL
                    , 2
                    , 2
                    , CXCHARS * sCharWidth
                    , CYCHARS * sCharHeight
                              + (SHORT)WinQuerySysValue( HWND_DESKTOP
                                                       , SV_CYTITLEBAR )
                              + (SHORT)WinQuerySysValue( HWND_DESKTOP
                                                       , SV_CYMENU )
                    , SWP_MOVE | SWP_SIZE );

     while (WinGetMsg( hab, &qmsg, NULL, 0, 0))
         WinDispatchMsg( hab, &qmsg);

 exit:                                            /* clean up */
     if (hdcSqr)                             /* square-root bitmap */
     {
         GpiDestroyPS( hpsSqr);
         if (hbmSqr)
             GpiDeleteBitmap( hbmSqr);
     }

     if (hdcLocal)                            /* keypad button */
     {
         GpiDestroyPS( hpsLocal);
         if (hbmLocal)
             GpiDeleteBitmap( hbmLocal);
     }

     WinDestroyWindow(hwndCalcFrame);

     WinDestroyMsgQueue(hmqCalc);
     WinTerminate(hab);

     DosExit(EXIT_PROCESS, 0);                    /* exit without error */
 }


 /****************************************************************************
 /* Calc Window Procedure
 /****************************************************************************
 MRESULT EXPENTRY
 CalcWndProc(hwnd, msg, mp1, mp2)

 HWND        hwnd;
 USHORT        msg;
 MPARAM        mp1;
 MPARAM        mp2;
 {
     HPS     hps;
     RECTL   rclPaint;
     WPOINT  wpt;
     BOOL    fClip;
     USHORT  usFmtInfo;
     RECTL   rcl;
     SIZEL   sizl;

     switch (msg)
     {
     case WM_CREATE:
         hdcCalc = WinOpenWindowDC( hwnd);
         WinQueryWindowRect( hwnd, &rcl);
         sizl.cx = rcl.xRight - rcl.xLeft;
         sizl.cy = rcl.yTop - rcl.yBottom;
         hpsCalc = GpiCreatePS( hab
                              , hdcCalc
                              , &sizl
                              , GPIA_ASSOC | PU_PELS );
         break;

     case WM_DESTROY:
         WinDestroyPointer(hptrFinger);
         GpiDestroyPS( hpsSqr);
         GpiDeleteBitmap( hbmSqr);
         GpiDestroyPS( hpsLocal);
         GpiDeleteBitmap( hbmLocal);
         break;

     case WM_INITMENU:
         fClip = FALSE;
         if (WinOpenClipbrd( hab))
         {
             fClip = WinQueryClipbrdFmtInfo( hab, CF_TEXT, &usFmtInfo);
             WinCloseClipbrd( hab);
         }
         WinSendMsg((HWND)mp2, MM_SETITEMATTR,
                    (MPARAM) MAKELONG(CMD_PASTE, TRUE),
                    (MPARAM) MAKELONG(MIA_DISABLED, fClip ? 0 : MIA_DISABLED))
         break;

     case WM_PAINT:
         hps = WinBeginPaint(hwnd, NULL, &rclPaint);
         CalcPaint( hwnd, hps);                            /* re-draw calculat
         WinEndPaint(hps);
         break;

     case WM_COMMAND:
         if (fError)
             break;
         switch(LOUSHORT(mp1))
         {
         case CMD_COPY:
             DataXCopy();                    /* copy to clipboard */
             break;
         case CMD_PASTE:
             DataXPaste();                    /* paste from clipboard */
             break;
         case CMD_EXIT:
             WinPostMsg( hwndCalcFrame, WM_QUIT, 0L, 0L);
             break;
         case CMD_ABOUT:
             WinDlgBox( HWND_DESKTOP
                      , hwndCalcFrame
                      , (PFNWP)AboutDlgProc
                      , NULL
                      , 1
                      , (PSZ)NULL );
             break;
         }
         break;

     case WM_CLOSE:
         WinPostMsg(hwndCalcFrame, WM_QUIT, 0L, 0L);
         break;

     case WM_MOUSEMOVE:
         WinSetPointer( HWND_DESKTOP, hptrFinger);
         break;

     case WM_BUTTON1DOWN:
         wpt.x = LOUSHORT(mp1);
         wpt.y = HIUSHORT(mp1);
         ProcessKey( &wpt);
         goto dwp;
         break;

     case WM_CHAR:
         if (SHORT1FROMMP(mp1) & KC_KEYUP)
         {
             if (CHAR4FROMMP(mp1) == uchMScan)
                    fMDown = FALSE;                 /* 'm' key went up */
         }
         else
         {
                 if (SHORT1FROMMP(mp1) & KC_CHAR)
       {
              if (InterpretChar((UCHAR)SHORT1FROMMP(mp2)))
                   {
                                 UpdateDisplay();
                   }
              else
                   {
                                   if (((UCHAR)SHORT1FROMMP(mp2)== 'm') || ((U
                             {
                                         uchMScan = CHAR4FROMMP(mp1);
                                         fMDown = TRUE;
                             }
         }
                 }
         }
         break;

     case WM_ACTIVATE:
         if (HIUSHORT(mp1))
             WinSetFocus( HWND_DESKTOP, hwndCalc);
         break;

     case WM_SETFOCUS:
         if ((HWNDFROMMP(mp1)==hwndCalc) && !mp2);
             fMDown = FALSE;                        /* since we are losing foc
         break;

 dwp:
     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }


 /****************************************************************************
 /*  translate & interpret keys (ie. locate in logical keyboard)
 /****************************************************************************
 BOOL
 InterpretChar( ch)

 CHAR ch;
 {
     BOOL fDone;
     NPCH pchStep;
     INT  i;

     fDone = FALSE;
     pchStep = achKeys;
     switch (ch)
     {
     case 'n':
         ch = szPlusMinus[0];
         break;
     case 27:                        /* xlate Escape into 'c' */
         ch = 'c';
         break;
     case '\r':                      /* xlate Enter into '=' */
         ch = '=';
         break;
     }

     if (fMDown)                     /* Do memory keys */
     {
         switch (ch)
         {
         case 'c':
         case 'C':
             ch = '\274';
             break;
         case 'r':
         case 'R':
             ch = '\273';
             break;
         case '+':
             ch = '\272';
             break;
         case '-':
             ch = '\271';
             break;
         }
     }

     while (!fDone && *pchStep)
     {
         if ((CHAR) *pchStep++ == ch)
             fDone = TRUE;                /* char found in logical keyboard */
     }
     if (fDone)
     {
         chLastKey = chCurrKey;
         i = pchStep - achKeys - 1;
         FlipKey( hpsCalc, i/6, i%6);
         Evaluate( achKeys[i]);
     }
     return (fDone);
 }


 /****************************************************************************
 /*  briefly reverse the shading on one of the keys
 /****************************************************************************
 VOID
 FlipKey( hps, iRow, iCol)

 HPS hps;
 INT iRow, iCol;
 {
     RECTL rcl;

     rcl.xLeft = (iCol * 6 * sCharWidth) + (14 * sCharWidth / 10);
     rcl.yBottom = (165 * sCharHeight / 100) + (2 * iRow * sCharHeight);
     rcl.xRight = rcl.xLeft + (11 * sCharWidth / 3);
     rcl.yTop = rcl.yBottom + (7 * sCharHeight / 4);
     WinInvertRect( hps, &rcl);
     DosSleep( 50L);
     WinInvertRect( hps, &rcl);
 }


 /****************************************************************************
 /*  compute whether a point is over a button and flash the button if so
 /****************************************************************************
 BOOL
 FlashSqr( hps, pwpt)

 HPS         hps;
 PWPOINT  pwpt;
 {
     INT  iRow, iCol;
     BOOL fDone;

     /* find x range */
     fDone = FALSE;
     iCol = 0;
     iRow = 3;
     while (!fDone && iCol<6)
     {
         if (pwpt->x <        (iCol * 6 * sCharWidth)
                        + (14 * sCharWidth / 10)
                        + (11*sCharWidth/3)         )
         {
             if (pwpt->x > (iCol * 6 * sCharWidth) + (14 * sCharWidth / 10))
                 fDone = TRUE;
             else
                 return FALSE;
         }
         else
             iCol++;
     }
     if (!fDone)
         return FALSE;
     fDone = FALSE;
     while (!fDone && iRow >= 0)
     {
         if (pwpt->y > ((165 * sCharHeight / 100) + (2 * iRow * sCharHeight)))
         {
             if (pwpt->y <   (165 * sCharHeight / 100)
                            + (2 * iRow * sCharHeight)
                            + (7 * sCharHeight / 4)     )
                 fDone = TRUE;
             else
                 return FALSE;
         }
         else
             iRow--;
     }
     if (!fDone)
         return FALSE;
     pwpt->x = iCol;
     pwpt->y = iRow;
     FlipKey( hps, iRow, iCol);
     return TRUE;
 }


 /****************************************************************************
 /*  which key is point on?
 /****************************************************************************
 CHAR
 Translate( pwpt)

 PWPOINT pwpt;
 {
     return( achKeys[ pwpt->y * 6 + pwpt->x]);
 }


 /****************************************************************************
 /*  invoke flashing, point-to-key translation, and result-display update
 /****************************************************************************
 VOID
 ProcessKey( pwpt)

 PWPOINT pwpt;
 {
     BOOL fFlashed;

     chLastKey = chCurrKey;
     fFlashed = FlashSqr( hpsCalc, pwpt);

     if (fFlashed)
         Evaluate( (BYTE)Translate( pwpt));
     UpdateDisplay();
 }


 /****************************************************************************
 /*  draw a blank key
 /****************************************************************************
 VOID
 FrameKey(hps, iRow, iCol)

 HPS hps;
 INT iRow, iCol;
 {
     POINTL aptl[3];

     aptl[0].x = (iCol * 6 * sCharWidth) + (14 * sCharWidth / 10);
     aptl[0].y = (165 * sCharHeight / 100) + (2 * iRow * sCharHeight);
     aptl[1].x = (11 * sCharWidth / 3) + (aptl[0].x);
     aptl[1].y = (7 * sCharHeight / 4) + (aptl[0].y);
     aptl[2].x = 0;
     aptl[2].y = 0;
     GpiBitBlt( hps, hpsLocal, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE);
 }


 /****************************************************************************
 /*  draw the keys and fill in numbers
 /****************************************************************************
 VOID
 DrawNumbers(hps)

 HPS hps;
 {
     INT iRow, iCol;

     /* Draw the keys and fill in the numbers we can */
     for (iRow = 0; iRow < 4; iRow++)
     {
         for (iCol = 0; iCol < 6; iCol++)
         {
             FrameKey( hps, iRow, iCol);
             CalcTextOut( hps
                        ,   (iCol * 6 * sCharWidth)
                          + (WIDTHCONST * sCharWidth / 10)
                        , (iRow + 1) * 2 * sCharHeight
                        , (PSZ)(achDKeys + (iRow * 6) + iCol)
                        , 1 );
         }
     }
 }


 /****************************************************************************
 /*  redraw the whole calculator
 /****************************************************************************
 VOID
 CalcPaint( hwnd, hps)

 HWND hwnd;
 HPS  hps;
 {
     RECTL      rclDst;
     CHARBUNDLE cbnd;
     INT        iX, iY;

     WinQueryWindowRect( hwnd, &rclDst);
     WinFillRect( hps, &rclDst, CLR_GREEN);

     DrawNumbers(hps);
     CalcTextOut(hps, iX = (11 * sCharWidth / 5) + 1, iY = 2 * sCharHeight,
                (PSZ)"M-", 2);
     CalcTextOut(hps, iX, iY + 2 * sCharHeight, (PSZ)"M+", 2);
     CalcTextOut(hps, iX, iY + 4 * sCharHeight, (PSZ)"MR", 2);
     CalcTextOut(hps, iX, iY + 6 * sCharHeight, (PSZ)"MC", 2);

     /* Draw the minus of the plus/minus button */
     cbnd.usBackMixMode = FM_LEAVEALONE;
     GpiSetAttrs( hps, PRIM_CHAR, CBB_BACK_MIX_MODE, 0L, &cbnd);
     iX =  (3 * 6 * sCharWidth) + (WIDTHCONST * sCharWidth / 10);
     CalcTextOut( hps, iX, iY + sCharHeight / 4, (PSZ)"_", 1);

     /* Draw the square root bitmap */
     rclDst.xLeft = 160 * sCharWidth / 5;
     rclDst.yBottom = 31 * sCharHeight / 4;
     rclDst.xRight = rclDst.xLeft + 2 * sCharWidth;
     rclDst.yTop = rclDst.yBottom + (3 * sCharHeight / 2);
     WinDrawBitmap( hps
                  , hbmSqr
                  , NULL
                  , (PPOINTL)&rclDst
                  , CLR_WHITE
                  , CLR_BLACK
                  , DBM_STRETCH );

     UpdateDisplay();
 }


 /****************************************************************************
 /*  initialize the bitmaps for a blank key and for the square-root sign
 /****************************************************************************
 BOOL
 PSInit()
 {
     HPS              hps;
     FONTMETRICS      fm;
     POINTL             ptl;
     SIZEL             sizl;
     BITMAPINFOHEADER bmp;
     POINTL             aptl[4];
     LONG             alCaps[2];

     /************************************************************************
     /*        compute the units of horizontal and vertical distance based on
     /************************************************************************
     hps = WinGetPS( HWND_DESKTOP);
     GpiQueryFontMetrics( hps, (LONG)sizeof(FONTMETRICS), &fm);
     sCharHeight = (SHORT)(fm.lEmHeight); /* avg height of uppercase character
     sCharWidth        = (SHORT)(fm.lEmInc);         /* usually 'M' increment
     WinReleasePS( hps);

     /************************************************************************
     /*        prepare the square root bitmap
     /************************************************************************
     hdcSqr = DevOpenDC( hab, OD_MEMORY, "*", 3L, (PDEVOPENDATA)&dop, NULL);
     if( !hdcSqr)
         return(FALSE);

     sizl.cx = sizl.cy = 0L;
     hpsSqr = GpiCreatePS( hab
                         , hdcSqr
                         , &sizl
                         , PU_PELS | GPIT_MICRO | GPIA_ASSOC );
     hbmSqr = GpiLoadBitmap( hpsSqr, NULL, IDB_SQR, 0L, 0L);

     /************************************************************************
     /*        prepare the bitmap of a blank key
     /************************************************************************
     hdcLocal = DevOpenDC( hab, OD_MEMORY, "*", 3L, (PDEVOPENDATA)&dop, NULL);
     if( !hdcLocal)
         return(FALSE);

     sizl.cx = sizl.cy = 0L;
     hpsLocal = GpiCreatePS( hab
                           , hdcLocal
                           , &sizl
                           , PU_PELS | GPIT_MICRO | GPIA_ASSOC );
     bmp.cbFix = 12;
     bmp.cx = 11 * sCharWidth / 3;
     bmp.cy = sCharHeight * 2;
     DevQueryCaps( hdcLocal, CAPS_COLOR_PLANES, 2L, alCaps);
     bmp.cPlanes = (USHORT)alCaps[0];
     bmp.cBitCount = (USHORT)alCaps[1];
     hbmLocal = GpiCreateBitmap( hpsLocal, &bmp, 0L, NULL, NULL);
     if( !hbmLocal )
         return(FALSE);
     GpiSetBitmap( hpsLocal, hbmLocal);

     aptl[0].x = aptl[0].y = 0;
     aptl[1].x = 11 * sCharWidth / 3;
     aptl[1].y = 7 * sCharHeight / 4;
     aptl[2].x = aptl[2].y = 0;
     aptl[3].x = aptl[1].x;
     aptl[3].y = aptl[1].y;
     GpiSetColor( hpsLocal, CLR_GREEN);            /* match the background to
     GpiBitBlt( hpsLocal, NULL, 2L, aptl, ROP_PATCOPY, BBO_IGNORE);

     /* Draw the rounded rect */
     ptl.x = 0;
     ptl.y = 0;
     GpiSetCurrentPosition( hpsLocal, &ptl);
     ptl.x = (11 * sCharWidth / 3) - 1;
     ptl.y = (7 * sCharHeight / 4) - 1;
     GpiSetColor( hpsLocal, CLR_WHITE);            /* white interior
     GpiBox( hpsLocal
           , DRO_FILL
           , &ptl
           , (LONG)sCharWidth
           , (LONG)(sCharHeight / 2) );
     ptl.x = 0;
     ptl.y = 0;
     GpiSetCurrentPosition( hpsLocal, &ptl);
     ptl.x = (11 * sCharWidth / 3) - 1;
     ptl.y = (7 * sCharHeight / 4) - 1;
     GpiSetColor( hpsLocal, CLR_BLACK);            /* black border
     GpiBox( hpsLocal
           , DRO_OUTLINE
           , &ptl
           , (LONG)sCharWidth
           , (LONG)(sCharHeight / 2) );
     return( TRUE);
 }


 CALCMATH.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CALC\CALCMATH.C

 /****************************** Module Header *******************************
 /*
 /* Module Name:  calcmath.c - Calc application
 /*
 /* OS/2 Presentation Manager version of Calc, ported from Windows version
 /*
 /* Created by Microsoft Corporation, 1987
 /*
 /****************************************************************************

 #define INCL_WINCLIPBOARD
 #include <os2.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>

 extern BOOL fValueInMemory;
 extern CHAR chLastKey;
 extern CHAR szreg1[20], szreg2[20], szmem[20];
 extern HWND hwndCalc;
 extern CHAR szregx[];
 extern HAB  hab;
 BOOL   fReadNumber;
 CHAR   PendingOperation;
 BOOL   fFirstOperand, fError;
 CHAR   szresult[20];
 SEL    sel;

 #define tolower(x)   (((x) >= 'A') && ((x)<='Z')) ? (x) - 'A' + 'a' : (x)
 #define MAXINT        (double)999999999
 #define MININT (double)-999999999
 #define ABS(x)                (((x) >= (double)0) ? (x) : (-(x)))


 /****************************************************************************
 extern VOID UpdateDisplay( VOID);
 extern BOOL InterpretChar( CHAR);

 VOID AppendNumber( BYTE);
 VOID BinaryOperator( CHAR);
 VOID Clear( VOID);
 VOID DataXCopy( VOID);
 VOID DataXPaste( VOID);
 VOID Equilibrate( VOID);
 VOID Evaluate( BYTE);
 VOID FarStrcpy( PSZ, PSZ);
 NPCH ftoa( double);
 VOID InitCalc( VOID);
 VOID MClear( VOID);
 VOID MMinus( VOID);
 VOID MPlus( VOID);
 VOID Negate( VOID);
 VOID Number( CHAR);
 VOID Percent( VOID);
 VOID reverse( NPCH);
 VOID Simplify( VOID);
 VOID SquareRoot( VOID);


 /****************************************************************************
 VOID FarStrcpy( pszDest, pszSrc)
 PSZ  pszDest, pszSrc;
 {
     while( *pszDest++ = *pszSrc++);
 }

 /****************************************************************************
 VOID
 reverse( s)

 NPCH s;
 {
     CHAR ch;
     register INT iHead, iTail;

     for (iHead = 0, iTail = strlen(s) - 1; iHead<iTail; iHead++, iTail-- ) {
         ch = s[iHead];
         s[iHead] = s[iTail];
         s[iTail] = ch;
     }
 }

 /****************************************************************************
 NPCH
 ftoa( dblNum)

 double dblNum;
 {
     sprintf( szresult, "%.8f", dblNum );
     return (szresult);
 }


 /****************************************************************************
 VOID
 Negate()
 {
     CHAR sztemp[ 20 ];

     if (szreg1[0] ==  '-')
         strcpy(szreg1, (&szreg1[1]));                     /* get rid of minus
     else if (szreg1[0] != '0' || (strlen(szreg1) > 2)) { /* can't negate zero
              sztemp[0] = '-';
              strcpy(&sztemp[1], szreg1);
              strcpy(szreg1, sztemp);
          }
 }

 /****************************************************************************
 VOID
 Number( ch)

 CHAR ch;
 {
     register INT iLen, iSize;

     iSize = 9;
     if (szreg1[0] == '-') iSize++;
     if (strchr(szreg1, '.')) iSize++;
     iLen  = strlen(szreg1 );
     if (iLen == iSize) return;
     if (iLen == 1 && szreg1[0] == '0') iLen--;
     szreg1[ iLen ] = ch;
     szreg1[min(iLen + 1, 11)] = 0;
 }

 /****************************************************************************
 VOID
 AppendNumber ( b)

 BYTE b;
 {
     if (b == '.') {                    /*        if no decimal, add one at en
         if (!strchr(szreg1, '.'))
             strcat(szreg1, ".");
     }
     else if ( b == 0xb1 )
              Negate();
          else
              Number(b);
 }

 /****************************************************************************
 VOID
 Equilibrate()
 {
     double dblResult;
     double dblX1, dblX2;

     if (chLastKey == '=') return;
     dblResult = (double)atof(szreg1);
     dblX1 = (double)atof(szreg1);
     dblX2 = (double)atof(szreg2);

     switch (PendingOperation) {
         case '+':
             if (dblX2>(double)0) {            /* check for overflow */
                 if (dblX1>(double)0) {
                     if (dblX1 > (MAXINT - dblX2))
                         fError = TRUE;
                 }
             }
             else if (dblX2 < (double)0) {
                      if (dblX1 < (double)0) {
                          if ( dblX1 < (MININT - dblX2))
                              fError = TRUE;
                      }
                  }
             if (!fError)
                 dblResult = dblX2 + dblX1;
             break;
         case '-':
             if (dblX2 < (double)0) {
                 if (dblX1 > (double)0) {
                     if (dblX1 > (dblX2 - MININT))
                         fError = TRUE;
                 }
             }
             else if (dblX2 > (double)0) {
                     if (dblX1 < (double)0) {
                         if (dblX1 < (dblX2 - MAXINT))
                             fError = TRUE;
                     }
                  }
             if (!fError)
                 dblResult = dblX2 - dblX1;
             break;
         case '/':
             if (dblX1 == (double)0.0)
                 fError = TRUE;
             else if (dblX2 > (double)0) {
                      if (dblX1 > (double)0) {
                          if (dblX1 < (dblX2 / MAXINT))
                              fError = TRUE;
                      }
                      else {  /* dblX1 < 0 here */
                         if (dblX1 > (dblX2 / MININT))
                              fError = TRUE;
                      }
                  }
                  else {  /* dblX2 < 0 here */
                      if (dblX1 < (double)0) {
                          if (dblX1 > (dblX2 / MAXINT))
                              fError = TRUE;
                      }
                      else { /* dblX1 > 0 here */
                          if (dblX1 < (dblX2 / MININT))
                              fError = TRUE;
                      }
                  }
             if (!fError)
                 dblResult = dblX2 / dblX1;
             break;
         case '*':
             if (dblX1 == (double)0) return;
             if (ABS(dblX2) > (double)1) {
                 if (ABS(dblX1) > (double)1) {
                     if (ABS(dblX1) > (MAXINT / ABS(dblX2)))
                         fError = TRUE;
                     }
                 }
             if (!fError) dblResult = dblX2 * dblX1;
             break;
         }
     if (!fError) {
         strcpy(szreg1, ftoa((double)dblResult));
         strcpy( szreg2, szreg1 );
         }
     Simplify();
 }

 /****************************************************************************
 VOID
 SquareRoot()
 {
     double dblResult;

     dblResult = (double)atof(szreg1);
     if (dblResult < 0.0) {
         fError = TRUE;
         return;
     }
     if ((dblResult == 0.0) || ((chLastKey == 'q') && (dblResult == 1.0)))
         return;
     if ((dblResult < (double) 1.00000002) && (dblResult > (double) 1.0))
         dblResult = (double)1.0;
     else
         dblResult = sqrt(dblResult);
     strcpy( szreg1, ftoa((double)dblResult));
     if (atof( szreg1 ) == 0.0)
         strcpy(szreg1, "0.");
     Simplify();
 }

 /****************************************************************************
 VOID
 BinaryOperator( ch)

 CHAR ch;
 {
     if (fFirstOperand) {
         fFirstOperand = FALSE;
         strcpy(szreg2, szreg1);
     }
     else {
         Equilibrate();
     }
     PendingOperation = ch;
 }

 /****************************************************************************
 VOID
 Clear()
 {
     fReadNumber = FALSE;
     fFirstOperand = TRUE;
     strcpy(szreg1, "0.");
     if (fError || chLastKey == 'c'){
         strcpy(szreg2, "0.");
         PendingOperation = NULL;
     }
     fError = FALSE;
 }

 /****************************************************************************
 /* trash out trailing zeros, if a '.' is in the number
 /* and leading zeros in all cases.
 /****************************************************************************
 VOID
 Simplify()
 {
     register INT iLen, iCount;
     CHAR         achLocal[20];

     iCount = 0;
     strcpy(achLocal, szreg1);
     if (atof(achLocal) != 0.0) {
         while (achLocal[iCount++] == '0');
         strcpy(szreg1, &achLocal[iCount-1] );
     }
     if (strchr(szreg1, '.')) {
         iLen = strlen(szreg1);
         while (szreg1[--iLen] == '0');
         szreg1[min( iLen + 1, 11)] = 0; /* null terminate */
     }
 }


 /****************************************************************************
 VOID
 DataXPaste()
 {
     PSZ           psz;
     ULONG          ulText;
     register CHAR ch;

     if (WinOpenClipbrd( hab))
     {
         ulText = WinQueryClipbrdData( hab, CF_TEXT);
         if (ulText)
         {
             psz = MAKEP( (SEL)ulText, 0);
             while (*psz)
             {
                 ch = (CHAR) (tolower(*psz));
                 if (ch == 'm')
                 {
                     psz++;
                     switch (tolower(*psz))
                     {
                         case '-':
                             ch = '\271';
                             break;
                         case '+':
                             ch = '\272';
                             break;
                         case 'r':
                             ch = '\273';
                             break;
                         case 'c':
                             ch = '\274';
                             break;
                         default:
                             ch = ' ';
                             break;
                     }
                 }
                 psz++;
                 InterpretChar(ch);
                 UpdateDisplay();
             }
         }
     }
     WinCloseClipbrd( hab);
     InterpretChar('=');
     UpdateDisplay();
 }


 /****************************************************************************
 VOID
 DataXCopy()
 {
     PSZ  pszText;

     if (WinOpenClipbrd( hab))
     {
         WinEmptyClipbrd( hab);
         DosAllocSeg( 20, (SEL FAR *)&sel, SEG_GIVEABLE);
         if (sel == NULL) return;
         pszText = MAKEP(sel, 0);
         FarStrcpy( pszText, (PSZ)szreg1);
         WinSetClipbrdData( hab, (ULONG)sel, CF_TEXT, CFI_SELECTOR);
         WinCloseClipbrd( hab);
     }
 }


 /****************************************************************************
 VOID
 MPlus()
 {
     double dblX1, dblX2, dblResult;

     dblX2 = atof(szmem);
     dblX1 = atof(szreg1);

     if (dblX2>(double)0) {            /* check for overflow */
         if (dblX1>(double)0) {
             if (dblX1 > (MAXINT - dblX2))
                 fError = TRUE;
         }
     }
     else if (dblX2 < (double)0) {
              if (dblX1 < (double)0) {
                  if ( dblX1 < (MININT - dblX2))
                      fError = TRUE;
              }
          }
     if (!fError) {
         dblResult = dblX2 + dblX1;
         strcpy( szmem, ftoa((double)dblResult));
     }
     if (dblResult == (double)0.0)
          fValueInMemory = FALSE;
     else fValueInMemory = TRUE;
 }

 /****************************************************************************
 VOID
 MClear()
 {
      strcpy(szmem, "0.");
      fValueInMemory = FALSE;
 }


 /****************************************************************************
 VOID
 MMinus()
 {
     double dblX1, dblX2, dblResult;

     dblX2 = atof(szmem);
     dblX1 = atof(szreg1);
     if (dblX2 < (double)0) {
         if (dblX1 > (double)0) {
             if (dblX1 > (dblX2 - MININT))
                 fError = TRUE;
         }
     }
     else if (dblX2 > (double)0) {
             if (dblX1 < (double)0) {
                 if (dblX1 < (dblX2 - MAXINT))
                     fError = TRUE;
             }
          }
     if (!fError) {
         dblResult = dblX2 - dblX1;
         strcpy( szmem, ftoa((double)dblResult));
     }
     if (dblResult == (double)0.0)
          fValueInMemory = FALSE;
     else fValueInMemory = TRUE;
 }

 /****************************************************************************
 VOID
 Evaluate( bCommand)

 BYTE bCommand;
 {
     switch( bCommand ) {
         case '0': case '1': case '2': case '3': case '4': case '5':
         case '6': case '7': case '8': case '9': case '.': case 0xb1:
         case 'n': /* n = 'negate'  from keyboard */
             if ( fReadNumber )
                 AppendNumber( bCommand );
             else {
                       /* if starting a new number */
                 if (bCommand != 0xb1)
                     strcpy(szreg1, "0");
                 AppendNumber( bCommand );
             }
             if (bCommand != 0xb1)
                 fReadNumber = TRUE;
             break;
         case '+': case '-': case '/': case '*': case 'p':
             BinaryOperator(bCommand);
             fReadNumber = FALSE;
             break;
         case '=':
             fReadNumber = FALSE;
             Equilibrate();
             PendingOperation = NULL;
             break;
         case 'q':
             SquareRoot();
             fReadNumber = FALSE;
             break;
         case 0xBB:   /* MR */
             strcpy(szreg1, szmem);
             fReadNumber = FALSE;
             Simplify();
             break;
         case 0xBA: /* M+ */
             MPlus();
             fReadNumber = FALSE;
             Simplify();
             break;
         case 0xB9: /* M- */
             MMinus();
             fReadNumber = FALSE;
             Simplify();
             break;
         case 0xBC:
             MClear(); /* MC */
             break;
         case '%':
             Percent();
             fReadNumber = FALSE;
             break;
         case 'c':
             Clear();
             break;
         }
 }

 /****************************************************************************
 VOID
 Percent()
 {
     double dblX1, dblX2, dblResult;

     dblX1 = atof(szreg1) / 100.0;
     dblX2 = atof(szreg2);
     if (ABS(dblX2) > (double)1) {
         if (ABS(dblX1) > (double)1) {
             if (dblX1 > (MAXINT / dblX2))
                 fError = TRUE;
         }
     }
     if (!fError) {
         dblResult = dblX2 * dblX1;
         strcpy( szreg1, ftoa((double)dblResult));
     }
     Simplify();
 }

 /****************************************************************************
 VOID
 InitCalc()
 {
     fReadNumber = FALSE;
     fError = FALSE;
     fFirstOperand = TRUE;
     PendingOperation = 0;
     strcpy(szreg1, "0.");
     strcpy(szmem,  "0.");
 }


 CASCADE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CASCADE\CASCADE.C

 #define INCL_PM
 #include <OS2.H>
 #include "Cascade.H"

 char szAppName[] = "Cascade";
 char szAppTitle[] = "Cascading Menu Example";

 HAB        hAB;
 HMQ        hmqMsgQueue;
 HWND        hWndMain,
         hWndFrame;

 int cdecl main()
 {
     QMSG    qmsg;
     ULONG   ctlData = FCF_STANDARD & ~FCF_ACCELTABLE;

     hAB = WinInitialize (0);

     hmqMsgQueue = WinCreateMsgQueue (hAB, 0);

     if (!WinRegisterClass (hAB,
                            szAppName,
                            WndProc,
                            CS_SYNCPAINT | CS_SIZEREDRAW,
                            0)) {
         return(0);
     }

     hWndFrame = WinCreateStdWindow ( HWND_DESKTOP,
                                     WS_VISIBLE,
                                     &ctlData,
                                     szAppName,
                                     NULL,
                                     0L,
                                     0,
                                     ID_RESOURCE,
                                     &hWndMain);
     WinSetWindowText (hWndFrame, szAppTitle);
     WinShowWindow (hWndFrame, TRUE);

     while ( WinGetMsg (hAB, &qmsg, NULL, 0, 0)) {
         WinDispatchMsg (hAB, &qmsg);
     }

     WinDestroyWindow   (hWndFrame);
     WinDestroyMsgQueue (hmqMsgQueue);
     WinTerminate       (hAB);
 }

 /*-------------------------------------------------------------------*/
 /*                                                                     */
 /*-------------------------------------------------------------------*/

 BOOL CheckAll (HWND hMenu, int item, BOOL check);
 BOOL CheckAll (HWND hMenu, int item, BOOL check)
 {
     int mPos,max,test;
     MENUITEM mi;
     char szText[20];
     MPARAM  mp1, mp2;

     max =(int) SHORT1FROMMR( WinSendMsg (hMenu, MM_QUERYITEMCOUNT, 0L, 0L) );

     for (mPos=0; mPos!=(int) max; mPos++) {
         test =(int) SHORT1FROMMR( WinSendMsg (hMenu, MM_ITEMIDFROMPOSITION, M
         WinSendMsg (hMenu, MM_QUERYITEMTEXT, MPFROM2SHORT(test,sizeof(szText)
         if (test == item) {
             mp1 = MPFROM2SHORT (test, TRUE);
             if (check)
                 mp2 = MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED);
             else
                 mp2 = MPFROM2SHORT(MIA_CHECKED, 0);
             WinPostMsg (hMenu, MM_SETITEMATTR, mp1, mp2);
             return TRUE;
         } else {
             WinSendMsg (hMenu, MM_QUERYITEM, MPFROM2SHORT(test,FALSE), (MPARA
             if (mi.hwndSubMenu) {
                 if (CheckAll(mi.hwndSubMenu, item, check)) {
                     mp1 = MPFROM2SHORT (test, TRUE);
                     if (check)
                         mp2 = MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED);
                     else
                         mp2 = MPFROM2SHORT(MIA_CHECKED, 0);
                     WinPostMsg (hMenu, MM_SETITEMATTR, mp1, mp2);
                     return TRUE;
                 }

             }
         }
     }
     return FALSE;
 }

 /*-------------------------------------------------------------------*/
 /*                                                                     */
 /*-------------------------------------------------------------------*/


 MRESULT EXPENTRY WndProc (hWnd, msg, mp1, mp2)
     HWND    hWnd;
     USHORT  msg;
     MPARAM  mp1, mp2;
 {
     HPS           hPS;
     HWND   hMenu;
     static int          prevFont = 0;
     int thisItem;

     switch (msg) {

         case WM_COMMAND:
             thisItem = SHORT1FROMMP(mp1);
             switch (thisItem) {
                 case IDM_ABOUT:
                     WinMessageBox (HWND_DESKTOP, hWnd,
                         "Sample PM Application",
                         szAppTitle, 1, MB_OK | MB_APPLMODAL | MB_MOVEABLE);
                     break;
                 default:
                     if ((thisItem >= IDM_FIRSTFONT) && (thisItem<= IDM_LASTFO
                         hMenu        = WinWindowFromID (
                                     WinQueryWindow (hWnd, QW_PARENT, FALSE),
                                     FID_MENU);
                         CheckAll (hMenu, prevFont, FALSE);
                         CheckAll (hMenu, thisItem, TRUE);
                         prevFont = thisItem;
                     } else {
                         DosBeep(600,60);
                     }
             }
             break;

         case WM_CLOSE:
             WinPostMsg (hWnd, WM_QUIT, 0L, 0L);
             break;

         case WM_ERASEBACKGROUND:
             return ((MRESULT) TRUE);
             break;

         case WM_PAINT:
             hPS = WinBeginPaint (hWnd, NULL, (PWRECT)NULL);
             WinEndPaint (hPS);
             break;

         default:
             return (WinDefWindowProc (hWnd, msg, mp1, mp2));
             break;
     }
     return 0L;
 }


 CHASER.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CHASER\CHASER.C

 /*  SWARM
  *  Created by Microsoft Corp. 1986
  *
  *  the idea behind this game is as follows:
  *
  *   You have a collection of objects in the center of the playing field
  *   that you are trying to protect (just one object in current version). You
  *   control your own movements with the mouse. A number of "chasers" start
  *   around the edges of the field and begin moving towards the objects
  *   you want to protect. If you move the mouse on top of a chaser and click
  *   the left button, the chaser will be killed and disappear from the screen
  *   But as you close in on the chaser, it will detect your presence and try
  *   to dodge you. Meanwhile the other chasers will continue to go after
  *   your objects. If one of the chasers reaches an object, it will begin
  *   dragging it away to the edge of the screen (currently the game just
  *   ends when the single object is reached). When all objects are dragged
  *   away, the game ends. If a chaser is killed while dragging an object, the
  *   object is left where it is and must be protected in place - player canno
  *   move objects. If you kill all the chasers, a new group of faster ones
  *   will be spawned (currently the speed is constant). Your score is how
  *   many chasers you can kill (no score currently kept), so there is no
  *   advantage in sitting on the object for long periods.
  *
  * Swarm demonstrates several capabilities of OS/2 and the philosphy behind
  * them.  This program is made of three components: Initialization, the
  * mouse driven thread and the attacker thread.  The attacker thread is
  * launched as many times as there are attackers in a game.  Launching
  * the attacker several times takes full advantage of the OS to schedule
  * resources.  The programmer can think of the problem as only one attacker.
  * The system handles multiple instances of the thread.
  *
  * As the main loop launches threads it puts an ID code into the thread's
  * stack.  The code is used to index into the universe data.
  *
  * A ram semaphore is used to control access to global data.
  *
  * This demonstration shows the use of the following OS/2 system calls:
  *
  * Tasking:               VIO API:              Mouse API:
  *
  *   DosSemRequest()         VioScrollUp()               MouOpen()
  *   DosSemClear()         VioWrtCellStr()       MouSetPtrPos()
  *   DosCreateThread()         VioSetCurType()       MouReadEventQue()
  *   DosExit()                 VioSetMode()
  *   DosSleep()
  */
 #include <os2def.h>
 #define INCL_DOSPROCESS
 #define INCL_DOSSEMAPHORES
 #include <bsedos.h>
 #define INCL_SUB
 #include <bsesub.h>
 #include <malloc.h>
 #undef NULL
 #include <stdio.h>

 #define  STACKSIZE  200

 #define  DANGERZONE  3

 #define  LONGNAP     500L
 #define  SHORTNAP    150L

  WAIT (-1L)                        /* Wait for ram Semaphore */

  CHASER    8                        /* Number of chasers */

  SCREEN_HEIGHT           24                /* Default screen size */
 #define  SCREEN_WIDTH           79

  GOAL univ[CHASER]                /* Macros for constant stuff */
 #define  ME univ[ID]
 #define  MOUSE univ[CHASER+1]

  ALIVE 1                        /* Flags for attackers/goal */
 #define  DEAD 0

 char   Chaser[2] = { 0xE8, 0x20 };   /* character and attribute */
 char        Prize[2] = { 0x03, 0x2C };   /* for our various objects */
 char        Blank[2] = { 0x20, 0x22 };
 char        Blood[2] = { 0x20, 0x44 };

 struct {                              /* Universe structure and array */
     int     row;                        /* univ[0] = chaser     */
     int     col;                        /* univ[n-1] = chaser     */
     int     state;                        /* univ[n] = GOAL */
 } univ[CHASER+1];                        /* univ[n+1]= MOUSE */

 short                ScreenHeight,                /* Screen attributes */
                 ScreenWidth;

 HMOU                Mouse;                        /* place for mouse handle *
 ULONG                Shortnap;                /* Sleep times for chasers */
 ULONG                Longnap;
 ULONG                Semaphore = 0;                /* Ram semaphore */

 struct _VIOCURSORINFO        NewCur;         /* struct for setting cursor typ
 struct _VIOCURSORINFO        OldCur;

 struct _VIOMODEINFO        modedata;        /* Data saves for VIO mode */
 struct _VIOMODEINFO        OldVioMode;

 /*
  * Define all procedures before main.
  */
 void Defender();
 void CleanUp();
 int InitGame();
 void chaserthread();
 int ParseCmdLine(int,char **);
 
 /*
  * main(ac,av)
  *
  * Top level procedure and MOUSE thread for the GAME demo.
  */
 int main(ac, av)
 int ac;
 char *av[];
 {
     /*
      * Parse the command line and perform some initialization.
      */
     if (ParseCmdLine(ac,av)) {
         printf("usage: %s [24|43] [F|M|S]\n",av[0]);
         DosExit(EXIT_THREAD,1);
     }
     if (InitGame())                /* Init game, exit if some problem */
         DosExit(EXIT_PROCESS,1);

     Defender();                 /* Run mouse loop (defend against the swarm)

     CleanUp();
 }
 
 /*
  * Defender()
  *
  * This is the main loop of the mouse control thread.
  *
  * The semaphore is used to prevent the other threads from time slicing
  * while this routine is examining and/or modifying the universe.  The
  * Semaphore is grabbed after the read of the Mouse queue so we don't tie
  * up the attackers while waiting for a mouse event.
  */
 void Defender()
 {
     USHORT ReadType = 1,        /* Wait for mouse events */
            alive,
            i;
     struct _MOUEVENTINFO  MouInfo;    /* mouse event packet structure */

     alive = CHASER;

     do {
         MouReadEventQue( &MouInfo, &ReadType, Mouse); /* read where mouse is

         DosSemRequest( &Semaphore, WAIT);

         if( MouInfo.fs & 1) {                      /* If the mouse has moved
             MOUSE.row = MouInfo.row;
             MOUSE.col = MouInfo.col;
         }
         if( MouInfo.fs & 4 ) {                       /* if left button presse
             for (i = 0; i < CHASER; i++ ) {
                 if( ( MOUSE.row == univ[i].row ) &&
                     ( MOUSE.col == univ[i].col ) &&  /* see if we hit one */
                     ( univ[i].state == ALIVE) ) {
                      univ[i].state = DEAD;

                      DosBeep(300,75);                     /* make a dying sou
                      DosBeep(600,75);
                      DosBeep(300,85);

                      alive--;                    /* Decrease number alive */
                      break;                    /* Can only kill one at a time
                 }
             }
         }
         if( MouInfo.fs & 16 )              /* If right button pressed... */
             break;                        /* End game, clean up */

         DosSemClear(&Semaphore);
     }
     while (GOAL.state == ALIVE && alive);    /* loop till all are dead */
 }
 
 /*
  * This thread manages the individual attackers.  It is spun off as
  * many times as needed for a game.
  *
  * The interaction of the mouse cursor and the chaser character is sort
  * of funny, hence the funny code, below.  The mouse cursor seems to
  * remember what was under it when it was written.  Hence we cannot erase
  * the chaser if the mouse is "sitting" on it.        If we do, then when the
  * mouse moves it will re-write the original object.  This shows up as
  * phantom chasers.
  */
 void far chasethread(ID)               /* code that controls each "chaser" */
 int ID;
 {
     short  row, col;               /* Our current position */
     short  deltaX, deltaY;     /* how far from the mouse are we? */
     short  danger;               /* flag to indicate not far enough! */
     short  m;                       /* general purpose indexes */


     /* Print out the initial chaser character */

     VioWrtCellStr( Chaser, 2, ME.row, ME.col, 0 );

     /*
      * Keep running as long as the goal and myself haven't been killed.
      */
     for (;;) {

         row = ME.row;                  /* Grab the current position */
         col = ME.col;
         /*
          * If mouse is sitting upon the chaser, do nothing.  Allow
          * the player some time to kill the chaser
          */
         if ((MOUSE.row == row) && (MOUSE.col == col)) {
             DosSleep( 1L );
             continue;
         }
         DosSemRequest(&Semaphore, WAIT);
         /*
          * If either the GOAL or Myself is dead, exit loop and clean up.
          * This wasn't tested in the for loop since we don't want to exit
          * if the MOUSE is sitting on the chaser.
          */
         if (ME.state != ALIVE || GOAL.state != ALIVE)
             break;

         deltaX = MOUSE.col - col;        /* calculate how far we are */
         deltaY = MOUSE.row - row;

         if (((deltaX < -DANGERZONE) || (DANGERZONE < deltaX)) ||
             ((deltaY < -DANGERZONE) || (DANGERZONE < deltaY))) {

             danger = 0;

             if(GOAL.row < row)                    /* Creep towards the GOAL *
                 row--;
             else if (GOAL.row > row)
                 row++;
             if(GOAL.col < col)
                 col--;
             else if(GOAL.col > col)
                 col++;
         }
         else {
             danger = 1;                     /* Run away from the mouse */

             if ((MOUSE.row > row) && (row > 0))
                 row--;
             else if ((MOUSE.row < row) && (row < ScreenHeight))
                 row++;
             if ((MOUSE.col > col) && (col < ScreenWidth))
                 col--;
             else if ((MOUSE.col < col) && (col > 0))
                 col++;
         }
         /*
          * A quick and Dirty hack to prevent chasers from merging
          */
         for (m = 0; m < CHASER; m++ ) {
             if (univ[m].state == ALIVE &&
                 univ[m].row == row &&
                 univ[m].col == col &&
                 m != ID) {
                row += 1;
                col += 3;
             }
         }
         /*
          * Zap the old chaser and print the new.  Release the semaphore
          * after this, there can be no undesirable interactions now.
          */
         VioWrtCellStr( Blank, 2, ME.row, ME.col, 0 );
         VioWrtCellStr( Chaser, 2, row, col, 0 );

         DosSemClear(&Semaphore);
         /*
          * Update the current location
          */
         ME.row = row;
         ME.col = col;
         /*
          * See if we have reached the GOAL, if so eat it and exit
          */
         if ((row == GOAL.row) && (col == GOAL.col)) {
             VioWrtCellStr( Blank, 2, row, col, 0 );
             DosBeep(600,175);
             DosBeep(1200,175);            /* if we reach the prize, let out a
             DosBeep(600,185);            /* paint the screen red and end the
             DosBeep(1200,175);
             VioScrollUp( 0, 0, -1, -1, -1, Blood, 0 );
             GOAL.state = DEAD;
         }
         /*
          * Sleep an amount of time that varies depending
          * upon the danger level
          */
         if( danger )
             DosSleep(Shortnap);
         else
             DosSleep(Longnap);

     }
     /*
      * chaser is now dead or the game is over.
      * Erase its body and terminate the thread.  Release the semaphore.
      */
     DosSemClear(&Semaphore);

     if (GOAL.state == ALIVE) {
         VioWrtCellStr(Blank, 2, ME.row, ME.col, 0 );
     }
     DosExit( EXIT_THREAD ,0);
 }
 
 /*
  * InitGame()
  *
  * Initialize the GOAL, MOUSE and the CHASERS, launch each chase thread.
  *
  * Returns an error if any internal processing errors
  */
 int InitGame()
 {
     struct _PTRLOC InitMouPos;
     void far chasethread();                /* code to control chasers */
     PBYTE Tstack;                        /* stack for new threads */
     unsigned chaseID;
     int i, rc;
     /*
      * Clear the screen.
      */
     VioScrollUp( 0, 0, -1, -1, -1, Blank, 0 );
     /*
      * Draw the prize
      */
     GOAL.row = ScreenHeight/2;
     GOAL.col = ScreenWidth /2;
     GOAL.state = ALIVE;
     VioWrtCellStr(Prize, 2, GOAL.row, GOAL.col, 0 );
     /*
      * Open the mouse pointer device and set it's location.
      */
     MouOpen( 0L, &Mouse );
     InitMouPos.row = GOAL.row;
     InitMouPos.col = GOAL.col;
     MouSetPtrPos( &InitMouPos, Mouse);
     MouDrawPtr(Mouse);
     /*
      * A simple minded initialization for the start of each chaser.
      * Some sort of random placement (based upon system time?) would
      * be nice.
      */
     univ[0].row = 0;  univ[0].col = 0;
     univ[1].row = 0;  univ[1].col = 25;
     univ[2].row = 0;  univ[2].col = 55;
     univ[3].row = 0;  univ[3].col = 79;
     univ[4].row = ScreenHeight;  univ[4].col = 0;
     univ[5].row = ScreenHeight;  univ[5].col = 25;
     univ[6].row = ScreenHeight;  univ[6].col = 55;
     univ[7].row = ScreenHeight;  univ[7].col = 79;
     /*
      * Grab the semaphore to prevent chaser from running until we are done.
      */
     DosSemRequest(&Semaphore, WAIT);

     for( i = 0; i < CHASER; i++ ) {                /* for each of our threads
         univ[i].state = ALIVE;                        /* Set each one alive *
         Tstack = (PBYTE)malloc(sizeof(int) * STACKSIZE);
         if (Tstack == NULL ) {                        /* Create a stack */
             printf( "thread %d stack malloc failed\n", i );
             return(1);
         }
         Tstack += sizeof(int)*STACKSIZE; /* set stack pointer to correct end
         *--Tstack = HIBYTE(i);
         *--Tstack = LOBYTE(i);                 /* Push the ID on as a paramet

         rc = DosCreateThread(chasethread, &chaseID, Tstack);
         if(rc) {
             printf( "create of thread %d failed, error: %d\n", i, rc );
             return (1);
         }
     }
     DosSemClear(&Semaphore);

     return (0);
 }
 
 /*
  * CleanUp()
  *
  * Routine to reset the Video modes back to where they were.
  * (As best as possible).
  */
 void CleanUp()
 {
     char blank[2];

     DosSleep(1L);             /* Yield the machine so attacker can clean up *
     VioSetMode( &OldVioMode, 0);
 /*
     blank[0] = ' ';
     blank[1] = OldVioMode.color;
     VioScrollUp( 0, 0, -1, -1, -1, blank, 0 );
 */
     VioSetCurType( &OldCur, 0);
     DosExit(EXIT_PROCESS,0);              /* Exit and terminate all threads.
 }
 
 /*
  * ParseCmdLine(ac, av)
  *
  * Parses the command line arguments and sets up the game accordingly
  *
  */
 int ParseCmdLine(ac,av)
 int ac;
 char **av;
 {
     struct _VIOMODEINFO modedata;
     int    VioMode;

     Longnap = LONGNAP;
     Shortnap = SHORTNAP;
     ScreenWidth = SCREEN_WIDTH;
     ScreenHeight = SCREEN_HEIGHT;
     VioMode = 25;

     while(--ac) {
         av++;
         switch(**av) {
             case 'f':
             case 'F':
                 Longnap = LONGNAP / 2;
                 Shortnap= SHORTNAP/ 2;
                 break;
             case 'm':
             case 'M':
                 Longnap = LONGNAP;
                 Shortnap= SHORTNAP;
                 break;
             case 's':
             case 'S':
                 Longnap = LONGNAP * 2;
                 Shortnap= SHORTNAP* 2;
                 break;
             case '4':            /* Assume 43 line mode was wanted */
                 ScreenHeight = 42;
                 ScreenWidth  = 79;
                 VioMode = 43;
                 break;
             case '2':
                 ScreenHeight = 24;
                 ScreenWidth  = 79;
                 VioMode = 25;
                 break;
             default:
                 return(1);
         }
     }

     VioGetCurType(&OldCur, 0);                /* Save old cursor */

     modedata.cb = sizeof(modedata); /* change mode as needed */
     VioGetMode( &modedata, 0);
     OldVioMode = modedata;
     modedata.row = VioMode;
     VioSetMode( &modedata, 0);

     NewCur.yStart = 0;
     NewCur.cEnd = 0;
     NewCur.cx = 1;
     NewCur.attr = -1;

     VioSetCurType( &NewCur, 0 );         /* make cursor go away */

     return (0);
 }


 CIRCLEQ.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\COMTALK\CIRCLEQ.C

 /*
     Circular Queue buffer implementation (which gets read by AVIO module)
     Created by Microsoft Corporation, 1989
 */
 #define INCL_DOSSEMAPHORES
 <os2.h>                /* Need USHORT for global.h */
 <string.h>                /* One strcpy call */
 #include "global.h"
 #include "circleq.h"

        TIMEOUT        1000L                /* A second */

 LineInfo aliRing[QUEUESIZE];        /* The Circular Queue...*/
 int  iHead, iTail;
 BOOL fFirst;                        /* Are we just starting? */
 LONG lSemMyQueue;                /* Queue lock */

 void LineCopy(Line, Line);
 void QueFill(void);

 #define QueLock()   DosSemRequest(&lSemMyQueue, -1L)
 #define QueUnlock() DosSemClear(&lSemMyQueue)

 #define Fix(n) (((n) >= 0) ? (n) : ((n) + QUEUESIZE))
 #define Circle(x)        ((x) % QUEUESIZE)
 #define Incr(x)         (x = Circle(x + 1))
 #define Decr(x)                (x = (x > 0) ? (x - 1) : (QUEUESIZE - 1))

 void QueFill(void) {
     int i, j;

     for (i = 0; i < 25; i++) {
         aliRing[i].cch = MAXLINELEN;
         for (j = 0; j < MAXLINELEN; j++)
             aliRing[i].szText[j] = (char) (((i * j) % 10) + '0');
     }
     iHead = 0; iTail = 24;
 }

 void QueInit(void) {
     int i;

     fFirst = TRUE;
     QueLock();
     iHead = 0; iTail = 0;
     for (i = 0; i < QUEUESIZE; i++) aliRing[i].cch = 0;
     QueUnlock();
 }

 void QueAdvance(int n) {
     QueLock();
     iHead = Circle(iHead + n);
     QueUnlock();
 }

 Line QueQuery(int LineNum) { return &aliRing[Circle(iHead + LineNum)]; }

 BOOL QueInsertLine(Line pli) {
 /*
     Return FALSE if we try to overwrite the head
 */
     QueLock();
     /*
         Initialize the queue
     */
     if (fFirst) fFirst = FALSE;
     /*
         Increment TAIL, act if queue full
         Overwrite if last entry was incomplete
     */
     else if (aliRing[iTail].fComplete && (Incr(iTail) == iHead)) {
         /*
             We are overflowing...
         */
         Decr(iTail);
         QueUnlock();
         return FALSE;
     }
     /*
         Insert the element
     */
     LineCopy(pli, &aliRing[iTail]);
     QueUnlock();
     return TRUE;
 }

 BOOL QueCompleteLine(void) { return aliRing[iTail].fComplete; }

 void LineCopy(Line pliSrc, Line pliDst) {
     int i;

     pliDst->fDrawn                = pliSrc->fDrawn;
     pliDst->fComplete                = pliSrc->fComplete;
     pliDst->cch                        = pliSrc->cch;
     for (i = 0; i < (int) pliSrc->cch; i++) pliDst->szText[i] = pliSrc->szTex
 }

 int QueUpdateHead(int nRows, BOOL bPage, BOOL bPaging) {
     int i, nLines;

     nLines = Fix(Circle(iTail - iHead));
     nLines = (nLines >= nRows) ? (nLines - nRows + 1) : 0;
     if ((nLines = Min(nLines, nRows)) > 0) {
         if (bPage) {
             if (nLines < nRows) {
                 QueLock();
                 for (i = nLines; i < nRows; i++)
                     aliRing[Circle(iHead + nRows + i)].cch = 0;
                 QueUnlock();
             }
             nLines = nRows;
         }
         else if (bPaging) nLines = 0;
         QueLock();
         iHead = Circle(iHead + nLines);
         QueUnlock();
     }
     return nLines;
 }

 Line QueLastLine(void) {
     QueLock();
     aliRing[iTail].szText[aliRing[iTail].cch] = '\0';
     QueUnlock();
     return &aliRing[iTail];
 }

 int QuePageUp(int nRows) {
     int i, nLines;

     QueLock();
     nLines = Min((QUEUESIZE - 1) - Fix(Circle(iTail - iHead)), nRows);
     if (nLines) {
         iHead = Fix(Circle(iHead - nLines));
         for (i = 0; i < nLines; i++)
             aliRing[Circle(iHead + nRows + i)].fDrawn = FALSE;
     }
     QueUnlock();
     return nLines;
 }


 CLIPFILE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLIPVIEW\CLIPFILE.C

 /*
  * CLIPFILE.C -- File handling for ClipView
  * Created by Microsoft Corporation, 1989
  *
  * This file contains one routine:  SaveClipboard(), which uses
  * the OPENDLG library to put up a File...Save... dialog box.
  *
  * After getting a file name, it tries to save the current rendered format.
  */
 #define INCL_BITMAPFILEFORMAT
 #define        INCL_DOSFILEMGR
 #define INCL_DOSMEMMGR
 #define INCL_GPIBITMAPS
 #define        INCL_GPIMETAFILES
 #define        INCL_WINCLIPBOARD
 #define        INCL_WINERRORS
 #include <os2.h>
 #include <opendlg.h>
 #include <string.h>
 #include "clipview.h"
 /*
  * Globals
  */
 extern HAB        vhab;                        /* Anchor block
 extern HWND        vhwndClient;                /* Main client area        */
 /*
     Macros
 */
 #define CHK(f) fSuccess = fSuccess && (f)
 #define LOADSTRING(id, sz) WinLoadString(vhab, (HMODULE) NULL, id, MAXLEN, sz
 /*
     Private function prototypes
 */
 BOOL SaveText(HFILE hf, PSZ pszText);

 BOOL SaveClipboard(HWND hwnd, USHORT usFormat) {
 /*
     Save the clipboard contents in several formats.
     The "Save BITMAP" code is similar to that in the LINEFRAC sample.
 */
     BOOL                fSuccess = TRUE;     /* Did we succeed in saving? */
     ULONG                hItem;                     /* Handle from QueryClipb
     /*
         Variables needed for File...Save... dialog.
     */
     DLF                 dlf;                     /* Dialog file */
     HFILE                hf;                     /* Handle to output file */
     UCHAR                szExt[8];             /* Default extension */
     UCHAR                szInst[MAXLEN];      /* Instructions */
     UCHAR                szMessage[MAXLEN];   /* Various messages */
     UCHAR                szTitle[MAXTITLELEN];/* Application title */
     /*
         Variables needed for saving Metafiles
     */
     HMF                 hmfCopy;             /* Clipboard metafile copy */
     /*
         Variables needed for saving BITMAPs
     */
     BITMAPINFOHEADER        bmp;                /* Header to be queried */
     HDC                 hdcMemory;        /* Memory DC for the BITMAP */
     HPS                 hpsMemory;        /* ...and it's associated PS */
     PBITMAPFILEHEADER        pbfh;                /* bmp + color table */
     POINTL                ptlOrigin;        /* Bitmap origin */
     SEL                 selBuffer;        /* Selector to actual BITMAP */
     SEL                 selHeader;        /* Selector for the BMP header */
     SIZEL                sizl;                /* Used in PS creation */
     ULONG                cbBuffer;        /* No. of bytes in buffer */
     USHORT                cbExtra;        /* No. of bytes in "final" segment
     USHORT                cbHeader;        /* No. of bytes in header */
     USHORT                cbWrite1;        /* No. of bytes to be written... *
     USHORT                cbWrite2;        /* ...in the two-part sel writes *
     USHORT                cbWritten;        /* No. of bytes actually written
     USHORT                cSegs;                /* No. of segments to write *
     USHORT                i;                /* Which segment is being written
     USHORT                usHugeShift;
     /*
         Open the clipboard
     */
     if (!WinOpenClipbrd(vhab))
         return FALSE;
     /*
         Get the clipboard data
     */
     if (hItem = WinQueryClipbrdData(vhab, usFormat)) {
         /*
             Put up the Save... file dialog with the appropriate extensions
         */
         switch (usFormat) {
             case CF_TEXT:
             case CF_DSPTEXT:         strcpy(szExt, "\\*.TXT");  break;

             case CF_BITMAP:
             case CF_DSPBITMAP:         strcpy(szExt, "\\*.BMP");  break;

             case CF_METAFILE:
             case CF_DSPMETAFILE: strcpy(szExt, "\\*.MET");  break;

             default:                 strcpy(szExt, "\\*.*");    break;
         }
         /*
             Put the string "Saving Format:  <format>" in the Save dialog box
         */
         GetFormatName(usFormat, szMessage);
         LOADSTRING(IDS_SAVETITLE, szTitle);
         strcat(szTitle, szMessage);

         LOADSTRING(IDS_APPNAME, szMessage);
         LOADSTRING(IDS_INST, szInst);

         SetupDLF(&dlf, DLG_SAVEDLG, &hf,
                  (PSZ) szExt, (PSZ) szMessage, (PSZ) szTitle, (PSZ) szInst);

         dlf.szFileName[0] = dlf.szOpenFile[0] = '\0';
         /*
             Put up a Save file dialog, and respond appropriately to
             the return status.
         */
         switch (DlgFile(hwnd, &dlf)) {
             case TDF_ERRMEM:
             case TDF_INVALID:
             case TDF_NOSAVE:
                 fSuccess = FALSE;

                 /* fall through... */
             default:
                 break;
         }

         if (fSuccess) {
           switch (usFormat) {

             case CF_TEXT:
             case CF_DSPTEXT:
                 CHK(SaveText(hf, MAKEP((SEL) hItem, 0)));
                 DosClose(hf);
                 break;

             case CF_BITMAP:
             case CF_DSPBITMAP:
                 /*
                     Initialize the Memory DC and its PS
                 */
                 sizl.cx = sizl.cy = 0L;
                 hdcMemory = DevOpenDC(vhab, OD_MEMORY, "*", 0L, NULL, NULL);
                 hpsMemory = GpiCreatePS(vhab, hdcMemory, &sizl,
                                   GPIA_ASSOC | GPIT_MICRO | PU_PELS);
                 /*
                     Draw the BITMAP into the Memory DC
                 */
                 CHK(GpiSetBitmap(hpsMemory, (HBITMAP) hItem) != HBM_ERROR);
                 ptlOrigin.x = ptlOrigin.y = 0L;
                 CHK(WinDrawBitmap(hpsMemory, (HBITMAP) hItem, NULL,
                            &ptlOrigin, CLR_BLACK, CLR_BACKGROUND, DBM_NORMAL)
                 /*
                     Get information about the BITMAP
                 */
                 CHK(GpiQueryBitmapParameters((HBITMAP) hItem, &bmp) == GPI_OK
                 /*
                     Compute the size of the buffer, and allocate
                     Make sure that > 64K BITMAPs are handled
                     (this code is from LFFILE.C)
                 */
                 cbBuffer = ( ((((ULONG)bmp.cBitCount*(ULONG) bmp.cx)+31L)/32L
                                 * 4L * (ULONG) bmp.cy * (ULONG) bmp.cPlanes )
                 cSegs   = (USHORT) (cbBuffer >> 16);
                 cbExtra = (USHORT) (cbBuffer & 0xFFFFL);
                 CHK(!DosAllocHuge(cSegs, cbExtra, &selBuffer, 0, 0));
                 CHK(!DosGetHugeShift(&usHugeShift));
                 /*
                     Compute the size of the BITMAPFILEHEADER + color table...
                     ...then allocate it.
                 */
                 cbHeader = (USHORT) (sizeof(BITMAPFILEHEADER)
                                 + (sizeof(RGB) << bmp.cBitCount));
                 CHK(!DosAllocSeg(cbHeader, &selHeader, SEG_NONSHARED));
                 pbfh = MAKEP(selHeader, 0);
                 /*
                     Copy the BITMAP information from the BITMAPINFOHEADER
                 */
                 pbfh->bmp.cbFix     = 12;
                 pbfh->bmp.cx            = bmp.cx;
                 pbfh->bmp.cy            = bmp.cy;
                 pbfh->bmp.cPlanes   = bmp.cPlanes;
                 pbfh->bmp.cBitCount = bmp.cBitCount;
                 /*
                     Get the actual BITMAP bits
                 */
                 CHK(GpiQueryBitmapBits(hpsMemory, 0L, (LONG) bmp.cy,
                        MAKEP(selBuffer, 0), (PBITMAPINFO) &(pbfh->bmp))
                     != GPI_ALTERROR);
                 /*
                     Set up the file header
                 */
                 pbfh->usType            = BFT_BMAP;
                 pbfh->cbSize            = cbHeader + cbBuffer;
                 pbfh->xHotspot            = bmp.cx / 2;        /* Anywhere wi
                 pbfh->yHotspot            = bmp.cy / 2;
                 pbfh->offBits            = cbHeader;
             /*
                 Blast the BITMAP to a file...
             */
                 /*
                     ...first, the header...
                 */
                 CHK(!DosWrite(hf, pbfh, cbHeader, &cbWritten));
                 /*
                     ...then, the possibly large BITMAP itself
                 */
                 for (i = 0; i <= cSegs; ++i) {
                     if (i < cSegs) {
                     /*
                         If we a 64K segment, write it in two
                         parts.         This must be done because
                         DosWrite() can only write 64K - 1
                         characters at once.
                     */
                         cbWrite1 = cbWrite2 = 0x8000;
                     } else {
                     /*
                         The last segment is always small enough
                         to write entirely.
                     */
                         cbWrite1 = cbExtra; cbWrite2 = 0;
                     }

                     if (cbWrite1) {
                         CHK(!DosWrite(hf,
                                 MAKEP((selBuffer + (i << usHugeShift)), 0),
                                 cbWrite1, &cbWritten));
                         if (cbWrite2) {
                             CHK(!DosWrite(hf,
                                 MAKEP((selBuffer + (i<<usHugeShift)),cbWrite1
                                 cbWrite2, &cbWritten));
                         }
                     }
                 }
                 /*
                     Clean up

                     Error codes are not checked here because the file has
                     already been saved.
                 */
                 DosClose(hf);
                 GpiSetBitmap(hpsMemory, NULL);
                 GpiDestroyPS(hpsMemory);
                 DevCloseDC(hdcMemory);
                 break;

             case CF_METAFILE:
             case CF_DSPMETAFILE:
                 /*
                     Save metafile

                     We close and delete the file, because GpiSaveMetaFile()
                     only allows the user to create a new file.

                     We copy the metafile because GpiSaveMetafile()
                     removes the data from the application's memory.
                 */
                 DosClose(hf);
                 CHK(!DosDelete(dlf.szFileName, 0L));
                 CHK((hmfCopy = GpiCopyMetaFile((HMF) hItem)) != GPI_ERROR);
                 CHK(GpiSaveMetaFile(hmfCopy, dlf.szFileName) != GPI_ERROR);
                 break;

             default:
                 /*
                     It may be reasonable to add support for other formats
                     here, by saving a bitmap of the current window contents.

                     But for now, close the file and return an error message.
                 */
                 DosClose(hf);
                 fSuccess = FALSE;
                 break;
           }
         }
     } else
         fSuccess = FALSE;        /* Couldn't query the clipboard format! */
     /*
         Clean up
     */
     WinCloseClipbrd(vhab);
     return fSuccess;
 }

 BOOL SaveText(HFILE hf, PSZ pszText) {
 /*
     Save text format

     Count the number of characters, then write them.
 */
     PSZ     pszCounter;     /* Temporary to count chars in sel */
     ULONG   ulcch = 0;            /* The number of characters */
     USHORT  cbWritten;            /* No. of bytes actually written */

     pszCounter = pszText;
     while (*pszCounter++) ulcch++;

     return(!DosWrite(hf, pszText, (USHORT) ulcch, &cbWritten));
 }


 CLIPVIEW.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLIPVIEW\CLIPVIEW.C

 /*
  * CLIPVIEW.C -- Clipboard Viewing application
  * Created by Microsoft Corporation, 1989
  *
  * This program registers itself as the clipboard viewer, if no clipboard
  * viewer exists.  Then, it intercepts WM_DRAWCLIPBOARD messages.
  *
  * This file contains the routines which handle the client/frame windows,
  * the dialog routines, and the clipboard rendering code.
  */
 #define        INCL_GPIBITMAPS
 #define        INCL_GPIMETAFILES
 #define INCL_WINATOM
 #define        INCL_WINCLIPBOARD
 #define        INCL_WINFRAMEMGR
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMENUS
 #define        INCL_WINMLE
 #define        INCL_WINSCROLLBARS
 #define        INCL_WINSYS
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include <string.h>
 #include "clipview.h"
 /*
  * Globals
  */
 BITMAPINFOHEADER vbmp;                        // Dimensions of current BITMAP
 BOOL        vfUpdate        = FALSE;        // Are we updating the clipboard?
 BOOL        vfViewBitmap        = FALSE;        // Are we currently viewing a
 HAB        vhab;                                // Anchor block
 HDC        vhdcMemory;                        // A memory DC for BitBlt-ing i
 HDC        vhdcWindow        = NULL;         // Client window DC
 HMQ        vhmqClip;                        // Message queue
 HPS        vhpsMemory;                        // A PS associated with vhdcMem
 HWND        vhwndClient;                        // Main client area
 HWND        vhwndClipFrame = NULL;                // Main frame window
 HWND        vhwndHSB        = NULL;         // Horizontal scroll bar
 HWND        vhwndMLE        = NULL;         // Handle to the MLE
 HWND        vhwndTitlebar        = NULL;         // Title-bar handle
 HWND        vhwndVSB        = NULL;         // Vertical scroll bar
 SHORT        vcMaxHSB;                        // Maximum scroll range for HSB
 SHORT        vcMaxVSB;                        // ...and for the VSB
 SHORT        vcUpdate        = -1;                // Counter for scroll bar u
 USHORT        vausFormats[MAXFORMATS];        // All available formats
 USHORT        vcFmts;                         // How many formats?
 USHORT        vusFormat;                        // What is the current format
 USHORT        vfsFmtInfo;                        // Clipboard Format Informat
 /*
     Macros
 */
 #define LOADSTRING(id, sz) WinLoadString(vhab, (HMODULE) NULL, id, MAXLEN, sz
 #define MESSAGE(sz) WinMessageBox(HWND_DESKTOP, vhwndClient, sz, NULL, 0, \
                         MB_OK | MB_ICONASTERISK | MB_SYSTEMMODAL);
 /*
  * Main routine...initializes window and message queue
  */
 int cdecl main( ) {
     QMSG    qmsg;                    /* Message queue */
     ULONG   ctldata;                    /* FCF_ flags */
     BOOL    fViewer;                    /* Does a viewer already exist? */
     UCHAR   szAlready[MAXLEN];            /* Already extant... message */
     UCHAR   szClassName[MAXLEN];    /* New class name */
     /*
         Start up our PM application
     */
     vhab = WinInitialize(0);
     vhmqClip = WinCreateMsgQueue(vhab, 0);
     /*
         We create the client window first to try to avoid
         synchronization problems.
     */
     LOADSTRING(IDS_CLIPCLASS, szClassName);
     if (!WinRegisterClass( vhab, (PCH)szClassName, (PFNWP)ClipWndProc,
                 CS_SIZEREDRAW, 0))
         return( 0 );
     /*
         Create the window (hidden)
     */
     ctldata = (FCF_STANDARD | FCF_HORZSCROLL | FCF_VERTSCROLL)
                             & ~(FCF_ACCELTABLE);

     vhwndClipFrame = WinCreateStdWindow( HWND_DESKTOP, WS_VISIBLE, &ctldata,
                                          szClassName, "",
                                          WS_VISIBLE, (HMODULE) NULL, ID_RESOU
                                          (PHWND) &vhwndClient );
     /*
         If there is no other clipboard viewer...
     */
     if (fViewer = !WinQueryClipbrdViewer(vhab, FALSE)) {
         /*
             ...we'll be the viewer.  Show the clipboard window.
         */
         WinSetClipbrdViewer(vhab, vhwndClient);
         /*
             Poll messages from event queue
         */
         while( WinGetMsg( vhab, (PQMSG)&qmsg, (HWND)NULL, 0, 0 ) )
             WinDispatchMsg( vhab, (PQMSG)&qmsg );
         /*
             Stop being the clipboard viewer.
         */
         if (vhwndMLE)
             WinDestroyWindow(vhwndMLE);
         WinSetClipbrdViewer(vhab, NULL);
     } else {
         /*
             ...otherwise, notify the user, then terminate.
         */
         LOADSTRING(IDS_ALREADY, szAlready);
         MESSAGE(szAlready);
     }
     /*
         Clean up
     */
     WinDestroyWindow( vhwndClipFrame );
     WinDestroyMsgQueue( vhmqClip );
     WinTerminate( vhab );

     return !fViewer;
 }

 MRESULT CALLBACK ClipWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2) {
 /*

  * This routine processes WM_COMMAND, WM_CREATE, WM_DRAWCLIPBOARD, WM_PAINT.
  * Everything else is passed to the Default Window Procedure.
  */
     HPS                hpsWindow;
     RECTL        rcl;
     SWP         swp;
     SIZEL        sizl;
     UCHAR        szMessage[MAXLEN];

     switch (msg) {

         case WM_CREATE:
             /*
                 Create a memory DC/PS to BitBlt BITMAPs around.
             */
             sizl.cx = sizl.cy = 0L;
             vhdcMemory = DevOpenDC(vhab, OD_MEMORY, "*", 0L, NULL, NULL);
             vhpsMemory = GpiCreatePS(vhab, vhdcMemory, &sizl,
                 GPIA_ASSOC | GPIF_DEFAULT | GPIT_MICRO | PU_PELS);
             break;

         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {
                 /*
                     About... dialog box
                 */
                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc,
                               (HMODULE) NULL, IDD_ABOUT, NULL);
                     return 0;
                 /*
                     Render... dialog box
                 */
                 case IDM_RENDER:
                     WinDlgBox(HWND_DESKTOP, hwnd, RenderDlgProc,
                               (HMODULE) NULL, IDD_RENDER, NULL);
                     return 0;
                 /*
                     Save... dialog box
                 */
                 case IDM_SAVE:
                     if (!SaveClipboard(hwnd, vusFormat)) {
                         LOADSTRING(IDS_NOTSAVED, szMessage);
                         MESSAGE(szMessage);
                     }
                     return 0;

                 default: break;
             }
             break;

         case WM_ERASEBACKGROUND:
             return (MRESULT) TRUE;
             break;

         case WM_PAINT:
             /* Open the presentation space */
             hpsWindow = WinBeginPaint(hwnd, NULL, &rcl);

             /* Fill in the background */
             WinFillRect(hpsWindow, &rcl, CLR_BACKGROUND);

             /* Paint in the clipboard */
             UpdateScreen(hwnd, vusFormat);

             /* Finish painting */
             WinEndPaint(hpsWindow);
             break;

         case WM_DRAWCLIPBOARD:
             /* Update the clipboard contents */
             GetAllFormats();
             vfUpdate = TRUE;
             WinPostMsg(hwnd, WM_PAINT, 0L, 0L);
             break;

         case WM_HSCROLL:
             if (vfViewBitmap) {
                 /*
                     Handle the appropriate scrolling messages
                 */
                 DoScrolling(hwnd, TRUE, HIUSHORT(mp2));
             } else
                 /*
                     If an ownerdraw format, let the owner handle it.
                 */
                 SendOwnerMsg(WM_HSCROLLCLIPBOARD, (MPARAM) hwnd, mp2);
             break;

         case WM_VSCROLL:
             if (vfViewBitmap) {
                 /*
                     Handle the appropriate scrolling messages
                 */
                 DoScrolling(hwnd, FALSE, HIUSHORT(mp2));
             } else
                 /*
                     If an ownerdraw format, let the owner handle it.
                 */
                 SendOwnerMsg(WM_VSCROLLCLIPBOARD, (MPARAM) hwnd, mp2);
             break;

         case WM_SIZE:
             /*
                 If the MLE is processing a text selector,
                 tell it to resize itself.  If we have
                 owner-draw data, tell the clipboard owner.
                 If we have a BITMAP, readjust the scroll
                 bar ranges.
             */
             if (vhwndMLE) {
                 WinQueryWindowPos(vhwndMLE, &swp);
                 swp.cx = SHORT1FROMMP(mp2);
                 swp.cy = SHORT2FROMMP(mp2);
                 WinSetMultWindowPos(vhab, &swp, 1);
             } else if (vfViewBitmap) {
                 WinQueryWindowPos(hwnd, &swp);
                 if ((vcMaxHSB = vbmp.cx - swp.cx) < 0)
                     vcMaxHSB = 0;
                 if ((vcMaxVSB = vbmp.cy - swp.cy) < 0)
                     vcMaxVSB = 0;
                 WinSendMsg(vhwndHSB, SBM_SETSCROLLBAR,
                     0L, MPFROM2SHORT(0, vcMaxHSB));
                 WinSendMsg(vhwndVSB, SBM_SETSCROLLBAR,
                     MPFROMSHORT(vcMaxVSB),
                     MPFROM2SHORT(0, vcMaxVSB));
             } else {
                 rcl.xLeft = rcl.yBottom = 0L;
                 rcl.xLeft = (LONG) SHORT1FROMMP(mp2) - 1;
                 rcl.yTop  = (LONG) SHORT2FROMMP(mp2) - 1;
                 SendOwnerMsg(WM_SIZECLIPBOARD, (MPARAM) hwnd, &rcl);
             }
             break;

         default:
             return WinDefWindowProc(hwnd, msg, mp1, mp2);
             break;
     }
     return 0L;
 }

 MRESULT CALLBACK RenderDlgProc(HWND hwndDlg, USHORT msg, MPARAM mp1, MPARAM m
 {
 /*
     Render... dialog procedure
 */
     HWND        hwndListbox;                /* Listbox of possible formats */
     UCHAR        szFmtName[MAXLEN];        /* Format name */
     UCHAR        szMessage[MAXLEN];
     USHORT        i;
     USHORT        usFormat;                /* Format to render */
     MRESULT        mrItem;                 /* Which listbox item selected? */

     switch(msg) {

         case WM_INITDLG:
             /*
                 Put all the possible formats into the listbox, and
                 select the first item by default.
             */
             hwndListbox = WinWindowFromID(hwndDlg, IDL_RENDER);
             WinSendMsg(hwndListbox, LM_DELETEALL, 0L, 0L);
             for (i = 0; i < vcFmts; i++) {
                 GetFormatName(vausFormats[i], szFmtName);
                 WinSendMsg(hwndListbox, LM_INSERTITEM,
                         MPFROMSHORT(LIT_END), MPFROMP((PVOID) szFmtName));
             }
             WinSendMsg(hwndListbox, LM_SELECTITEM, 0L, MPFROMSHORT(TRUE));
             break;

         case WM_CONTROL:
             /*
                 If the user makes a selection, quit!
             */
             if ((SHORT1FROMMP(mp1) == IDL_RENDER)
                 && (SHORT2FROMMP(mp1) == LN_ENTER))
                     WinPostMsg(hwndDlg, WM_COMMAND, MPFROMSHORT(DID_OK), 0L);
             break;

         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK:
                     /*
                         Since the user chose a selection, try to render it.
                     */
                     hwndListbox = WinWindowFromID(hwndDlg, IDL_RENDER);
                     mrItem = WinSendMsg(hwndListbox, LM_QUERYSELECTION, 0L, 0
                     if (mrItem != (MRESULT) LIT_NONE) {
                         usFormat = vausFormats[SHORT1FROMMR(mrItem)];
                         if (usFormat != vusFormat) {
                             /*
                                 If the clipboard format is not rendered,
                                 tell the user.
                             */
                             vfUpdate = TRUE;
                             if (!UpdateScreen(vhwndClient, usFormat)) {
                                 LOADSTRING(IDS_NODISPLAY, szMessage);
                                 MESSAGE(szMessage);
                             }
                         }
                     }

                     /* fall through */

                 case DID_CANCEL:
                     WinDismissDlg(hwndDlg, TRUE);

                 default: break;
             }
         default: return WinDefDlgProc(hwndDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 MRESULT CALLBACK AboutDlgProc(HWND hwndDlg, USHORT msg, MPARAM mp1, MPARAM mp
 {
 /*
     About... dialog procedure
 */
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hwndDlg, TRUE);
                 default: break;
             }
         default: return WinDefDlgProc(hwndDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 VOID ReadSelector(HWND hwndMLE, PSZ pszText) {
 /*
     Compute the length of the text selector, in bytes.
     Allocate space, and copy the text selector into an MLE.
 */
     IPT ipt;
     ULONG ulcch = 0;
     PSZ        pszCounter;

     pszCounter = pszText;
     while (*pszCounter++) ulcch++;
     WinSendMsg(hwndMLE, MLM_FORMAT, MPFROMSHORT(MLFIE_CFTEXT), 0L);
     WinSendMsg(hwndMLE, MLM_SETIMPORTEXPORT, pszText, (MPARAM) ulcch);
     WinSendMsg(hwndMLE, MLM_IMPORT, &ipt, (MPARAM) ulcch);
 }

 VOID FixFrame(VOID) {
 /*
     This routine tells the frame to update the scroll bars.

     First, make it so that the scroll bars cannot update themselves.
     Let the frame update the controls.  Then, re-enable the scroll bars.
 */
     if (!(vcUpdate--)) {
         WinEnableWindowUpdate(vhwndHSB, FALSE);
         WinEnableWindowUpdate(vhwndVSB, FALSE);
     }

     WinSendMsg(vhwndClipFrame, WM_UPDATEFRAME, MPFROMLONG(FCF_HORZSCROLL), 0L
     WinSendMsg(vhwndClipFrame, WM_UPDATEFRAME, MPFROMLONG(FCF_VERTSCROLL), 0L

     if (!(++vcUpdate)) {
         WinEnableWindowUpdate(vhwndHSB, TRUE);
         WinEnableWindowUpdate(vhwndVSB, TRUE);
     }
 }

 VOID NeedScrollBars(BOOL fNeed) {
 /*
     This routine hides changes the scroll bar state to correspond with
     fNeed, showing or hiding them as necessary.
 */
     static BOOL fNeeded = TRUE;                /* The last scroll bar state *

     /*
         Get the scroll bar handles, if we haven't already.
     */
     if (!vhwndHSB) {
         vhwndHSB = WinWindowFromID(vhwndClipFrame, FID_HORZSCROLL);
         vhwndVSB = WinWindowFromID(vhwndClipFrame, FID_VERTSCROLL);
     }
     /*
         Case 1:  We need scroll bars, so enable them.
     */
     if (fNeed) {
         if (!fNeeded) {
             WinSetParent(vhwndHSB, vhwndClipFrame, TRUE);
             WinSetParent(vhwndVSB, vhwndClipFrame, TRUE);
             FixFrame();
         }
     /*
         Case 2:  We don't need scroll bars, so hide them.
     */
     } else {
         if (fNeeded) {
             WinSetParent(vhwndHSB, HWND_OBJECT, TRUE);
             WinSetParent(vhwndVSB, HWND_OBJECT, TRUE);
             FixFrame();
         }
     }
     /*
         Save state for next invocation
     */
     fNeeded = fNeed;
 }

 /*
     RenderFormat()

     Input:                Clipboard format to render, and handle to client ar
     Side effects:        Renders the image in the client area
 */
 BOOL RenderFormat(HWND hwnd, USHORT usFormat) {
     BOOL    fRendered = TRUE;
     HMF            hmfCopy;
     HPS     hpsWindow;
     LONG    alOptions[8];
     RECTL   rclWindow;
     SIZEL   sizl;
     SWP            swpWindow;
     ULONG   hItem;
     POINTL  aptl[3];
     /*
         Open the clipboard
     */
     if (!WinOpenClipbrd(vhab))
         return FALSE;
     /*
         Open up the window DC and PS
     */
     if (!vhdcWindow)
         vhdcWindow = WinOpenWindowDC(hwnd);

     sizl.cx = sizl.cy = 0L;
     hpsWindow = GpiCreatePS(vhab, vhdcWindow, &sizl, GPIA_ASSOC | PU_ARBITRAR
     /*
         Enable the scroll bars, if necessary.  This affects the size
         of the client area.
     */
     if (vfUpdate)
         NeedScrollBars( (vfViewBitmap =
                 (usFormat == CF_BITMAP) || (usFormat == CF_DSPBITMAP)) );

     WinQueryWindowRect(hwnd, &rclWindow);
     /*
         Get the clipboard data
     */
     WinQueryClipbrdFmtInfo(vhab, usFormat, &vfsFmtInfo);
     if (!(hItem = WinQueryClipbrdData(vhab, usFormat))) {
         fRendered = FALSE;
     } else {
       /*
         Display the new format, as appropriate.
       */
       switch (usFormat) {
         case CF_TEXT:
         case CF_DSPTEXT:
             if (vfUpdate) {
                 /*
                     Create a new MLE and read the text into it.
                 */
                 vhwndMLE = WinCreateWindow(hwnd, WC_MLE, "",
                     WS_VISIBLE | MLS_READONLY | MLS_HSCROLL | MLS_VSCROLL,
                     0, 0,
                     (SHORT) rclWindow.xRight, (SHORT) rclWindow.yTop,
                     hwnd, HWND_TOP, 0, NULL, NULL);

                 ReadSelector(vhwndMLE, MAKEP((SEL) hItem, 0));
             }
             break;

         case CF_BITMAP:
         case CF_DSPBITMAP:
             if (vfUpdate) {
                 /*
                     Get the BITMAP dimensions, for scroll bar processing
                 */
                 if (GpiQueryBitmapParameters((HBITMAP) hItem, &vbmp)
                         != GPI_OK) {
                     return FALSE;
                 }
                 /*
                     Set the scroll bar ranges from 0 to vbmp.max - client.max
                 */
                 WinQueryWindowPos(hwnd, &swpWindow);

                 if ((vcMaxHSB = vbmp.cx - swpWindow.cx) < 0)
                     vcMaxHSB = 0;
                 if ((vcMaxVSB = vbmp.cy - swpWindow.cy) < 0)
                     vcMaxVSB = 0;
                 WinSendMsg(vhwndHSB, SBM_SETSCROLLBAR,
                     0L, MPFROM2SHORT(0, vcMaxHSB));
                 WinSendMsg(vhwndVSB, SBM_SETSCROLLBAR,
                     MPFROMSHORT(vcMaxVSB),
                     MPFROM2SHORT(0, vcMaxVSB));
             }
             /*
                 Draw the BITMAP, based on the scroll bar settings.
             */
             GpiSetBitmap(vhpsMemory, (HBITMAP) hItem);

             aptl[0].x = rclWindow.xLeft;        /* Target bottom left */
             aptl[0].y = rclWindow.yBottom;
             aptl[1].x = rclWindow.xRight;        /* Target top right */
             aptl[1].y = rclWindow.yTop;
                                                 /* Source bottom left */
             aptl[2].x = (LONG) WinSendMsg(vhwndHSB, SBM_QUERYPOS, 0L, 0L);
             aptl[2].y = vcMaxVSB
                 - (LONG) WinSendMsg(vhwndVSB, SBM_QUERYPOS, 0L, 0L);

             GpiBitBlt(hpsWindow, vhpsMemory, 3L, aptl, ROP_SRCCOPY, 0L);
             GpiSetBitmap(vhpsMemory, NULL);
             break;

         case CF_METAFILE:
         case CF_DSPMETAFILE:
             /*
                 Set up the alOptions for displaying the metafile, and
                 let the system do the rest of the work.
             */
             alOptions[PMF_SEGBASE]            = 0L;
             alOptions[PMF_LOADTYPE]            = LT_DEFAULT;
             alOptions[PMF_RESOLVE]            = 0L;
             alOptions[PMF_LCIDS]            = LC_LOADDISC;
             alOptions[PMF_RESET]            = RES_DEFAULT;
             alOptions[PMF_SUPPRESS]            = SUP_DEFAULT;
             alOptions[PMF_COLORTABLES]            = CTAB_NOMODIFY;
             alOptions[PMF_COLORREALIZABLE]  = CREA_DEFAULT;
             hmfCopy = GpiCopyMetaFile((HMF) hItem);
             GpiPlayMetaFile(hpsWindow, hmfCopy, 8L, alOptions, 0L, 0L, NULL);
             break;

         case CF_EMPTY:
             /*
                 Don't do anything.
             */
             break;

         default:
             /*
                 If it's an owner-draw format that we can display...
                 ...try to get the owner to paint the clipboard.
                 (return if we were successful or not)
             */
             fRendered = SendOwnerMsg(WM_PAINTCLIPBOARD, MPFROMHWND(hwnd), 0L)
             break;
       }
     }
     /*
         Tell everybody that the client area is valid now
     */
     WinValidateRect(hwnd, (PRECTL) NULL, FALSE);
     /*
         Clean up
     */
     GpiAssociate(hpsWindow, NULL);
     GpiDestroyPS(hpsWindow);
     WinCloseClipbrd(vhab);
     return fRendered;
 }

 BOOL UpdateScreen(HWND hwnd, USHORT usFormat) {
 /*
     Render the format, change the title bar.
     The title bar will look like:  "<appname> (<format>)"
 */
     BOOL  fRendered = TRUE;
     HPS   hpsWindow;
     RECTL rcl;
     UCHAR szFormat[MAXLEN];
     UCHAR szTitle[MAXTITLELEN];

     if (vfUpdate) {
         /* If the MLE exists, destroy it */
         if (vhwndMLE) {
             WinDestroyWindow(vhwndMLE);
             vhwndMLE = NULL;
         }

         /* Clear the client area */
         WinQueryWindowRect(hwnd, &rcl);
         WinInvalidateRect(hwnd, &rcl, FALSE);
         hpsWindow = WinBeginPaint(hwnd, NULL, NULL);
         WinFillRect(hpsWindow, &rcl, CLR_BACKGROUND);
         WinEndPaint(hpsWindow);
     }
     if (usFormat)                        // Check that usFormat != CF_EMPTY
         fRendered = RenderFormat(hwnd, usFormat);
     /*
         Set the title bar appropriately
     */
     if (!vhwndTitlebar && vhwndClipFrame)
         vhwndTitlebar = WinWindowFromID(vhwndClipFrame, FID_TITLEBAR);

     if (vhwndTitlebar) {
         GetFormatName(usFormat, szFormat);
         LOADSTRING(IDS_APPNAME, szTitle);
         strcat(szTitle, "("); strcat(szTitle, szFormat); strcat(szTitle, ")")
         WinSetWindowText(vhwndTitlebar, szTitle);
     }
     /*
         Save the rendered format.
     */
     vusFormat = usFormat;
     return fRendered;
 }

 VOID GetAllFormats(VOID) {
     USHORT usFormat;                // Temporary used when enumerating
     /*
         Put ourselves into a clean state
     */
     usFormat = vcFmts = 0;
     /*
         Cycle through the available clipboard formats
     */
     while (usFormat = WinEnumClipbrdFmts(vhab, usFormat)) {
         vausFormats[vcFmts++] = usFormat;
     }
     /*
         Set the current clipboard format to the first one, if possible
         (in preparation for the WM_PAINT which will follow).
     */
     vusFormat = (vcFmts ? vausFormats[0] : CF_EMPTY);
 }

 VOID GetFormatName(USHORT usFormat, UCHAR szFmtName[]) {
 /*
     GetFormatName()

     This routine returns a format name in szFmtName which corresponds
     to the format usFormat.  Basically, either we know the format, or
     we get the name from the system atom table.  If we can't find it,
     we set it to CF_UNKNOWN.
 */
     switch (usFormat) {
         /*
             If we know the format, we can read it from the string table.
         */
         case CF_EMPTY:
         case CF_TEXT:
         case CF_DSPTEXT:
         case CF_BITMAP:
         case CF_DSPBITMAP:
         case CF_METAFILE:
         case CF_DSPMETAFILE:
             LOADSTRING(usFormat, szFmtName);
             break;

         default:
             /*
                 Get the format name from the system atom table.
                 If not found, tag it as an unknown format.
             */
             if (!WinQueryAtomName(WinQuerySystemAtomTable(),
                    usFormat, szFmtName, MAXLEN))

                 LOADSTRING(CF_UNKNOWN, szFmtName);

             break;
     }
 }

 BOOL SendOwnerMsg(USHORT msg, MPARAM mp1, MPARAM mp2) {
     BOOL    rc;
     HWND    hwndOwner;
     /*
         If we are an OWNERDISPLAY format,
             lock the owner window, tell it to perform the operation, return
     */
     if ( rc = ( (vfsFmtInfo & CFI_OWNERDISPLAY)
          && (hwndOwner = WinQueryClipbrdOwner(vhab, TRUE)) ) ) {

         WinSendMsg(hwndOwner, msg, mp1, mp2);
         WinLockWindow(hwndOwner, FALSE);
     }
     return rc;
 }

 BOOL DoScrolling(HWND hwnd, BOOL fHorz, USHORT sbCmd) {
 /*
     This routine depends on the fact that the thumb cannot be set past the
     range of the scroll bar.  Since this is handled in the system SBM_SETPOS
     code already, we need not worry about it.

     We return TRUE if the scroll bar message is processed.
 */
     HWND   hwndSB;                /* Scroll bar handle */
     USHORT cpels;                /* Page length/width for PAGExxxx commands *
     SWP    swp;                        /* Dimensions of the client area */
     USHORT usOld;                /* The current scroll bar position */
     USHORT usNew;                /* The new scroll bar position */
     /*
         Set the scroll bar-specific parameters
     */
     WinQueryWindowPos(hwnd, &swp);
     if (fHorz) {        /* Horizontal scroll bar */
         hwndSB = vhwndHSB;
         cpels = swp.cx;
     } else {                /* Vertical scroll bar */
         hwndSB = vhwndVSB;
         cpels = swp.cy;
     }
     /*
         Handle both scroll bars with one common routine

         Basically, the scroll bar has been set so that
         the thumb value corresponds to the offset that
         the bitmap is drawn from.  So, to scroll by a
         page, compute the number of pels of the page,
         and move the thumb by that amount.

         This code is simplified by the fact that SB_SETPOS
         will not allow the thumb to be set outside of the
         range of the scroll bar, but will "stop" it at the
         appropriate bound.
     */
     usOld = SHORT1FROMMR( WinSendMsg(hwndSB, SBM_QUERYPOS, 0L, 0L));

     switch (sbCmd) {
         case SB_PAGERIGHT:        /* SB_PAGEDOWN */
             WinSendMsg(hwndSB, SBM_SETPOS, MPFROMSHORT(usOld + cpels), 0L);
             break;

         case SB_PAGELEFT:        /* SB_PAGEUP */
             WinSendMsg(hwndSB, SBM_SETPOS, MPFROMSHORT(usOld - cpels), 0L);
             break;

         case SB_LINERIGHT:        /* SB_LINEDOWN */
             WinSendMsg(hwndSB, SBM_SETPOS, MPFROMSHORT(usOld + LINE), 0L);
             break;

         case SB_LINELEFT:        /* SB_LINEUP */
             WinSendMsg(hwndSB, SBM_SETPOS, MPFROMSHORT(usOld - LINE), 0L);
             break;

         case SB_SLIDERPOSITION:
             /*
                 It would be nice to be consistent with the other
                 SB_ cases, but the problem is that when this message
                 is sent, the position is *already* set to "usPosition".

                 So, just invalidate the entire region, and hope that most
                 of these types of operations will be large scrolls.
             */
             // WinSendMsg(hwndSB, SBM_SETPOS, MPFROMSHORT(LOUSHORT(mp2)), 0L)
             WinInvalidateRect(hwnd, NULL, TRUE);
             break;

         default:
             return FALSE;
     }
     /*
         Now, we find out where the new thumb position is,
         scroll the window contents appropriately, and specify
         SW_INVALIDATERGN so that the remainder will be
         invalidated/repainted.
     */
     usNew = SHORT1FROMMR( WinSendMsg(hwndSB, SBM_QUERYPOS, 0L, 0L));
     if (fHorz)
         WinScrollWindow(hwnd, (SHORT) (usOld - usNew), 0,
             NULL, NULL, NULL, NULL, SW_INVALIDATERGN);
     else
         WinScrollWindow(hwnd, 0, (SHORT) (usNew - usOld),
             NULL, NULL, NULL, NULL, SW_INVALIDATERGN);

     return TRUE;
 }


 CLOCK.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLOCK\CLOCK.C

 /*
     clock.c        Presentation Manager Analog Clock Application
     Created by Microsoft Corporation, 1989
 */
 #define INCL_PM
 #include <os2.h>
 #include <string.h>
 #include "res.h"

 extern MRESULT EXPENTRY ClkWndProc ( HWND , USHORT , MPARAM , MPARAM ) ;

 int cdecl main ( int argc , char * argv [ ] ) ;
 BOOL ClkInit ( VOID ) ;
 HWND hwndFrame ;
 HAB hab ;
 HMQ hmq ;
 HSWITCH hsw ;
 extern HPS hps ;
 BOOL fStartAsIcon = FALSE ;

 /*
     main(argc, argv)        Main program
 */
 int cdecl main ( int argc , char * argv [ ] )
 {
     QMSG qmsg ;

     /* have we been asked to start ourselves as an icon? */
     if (argc > 0) {
         if ( strcmpi ( argv [ 1 ] , "iconic" ) == 0 )
             fStartAsIcon = TRUE ;
     }

     if ( ClkInit ( ) ) {

         while ( WinGetMsg ( hab , & qmsg , NULL , 0 , 0 ) )
             WinDispatchMsg ( hab , & qmsg ) ;

         /* Clean up code */
         GpiDestroyPS( hps );
         WinRemoveSwitchEntry ( hsw ) ;
         WinDestroyWindow ( hwndFrame ) ;
         WinDestroyMsgQueue ( hmq ) ;
         WinTerminate ( hab ) ;
     }

     return 0 ;
 }

 /*
     ClkInit()                Clock Initialization routine
     Returns TRUE if successful.
 */
 BOOL ClkInit ( )
 {
     /* application name, switch list info, and frame creation flags */
     static PSZ pszClkName = "Clock" ;
     static SWCNTRL swctl = { 0 , 0 , 0 , 0 , 0 , SWL_VISIBLE ,
                              SWL_JUMPABLE , "Clock" , 0 } ;
     static LONG fcf = FCF_SIZEBORDER | FCF_TITLEBAR | FCF_MINMAX
                       | FCF_SYSMENU ;

     HWND hwndClient ;
     PID pid ;
     TID tid ;

     if ( ( hab = WinInitialize ( 0 ) ) == NULL )
         return FALSE ;

     if ( ( hmq = WinCreateMsgQueue ( hab , 0 ) ) == NULL ) {
         WinTerminate ( hab ) ;
         return FALSE ;
     }

     if ( ! WinRegisterClass ( hab , pszClkName , ClkWndProc ,
                               CS_SIZEREDRAW , 0 ) ) {
         WinDestroyMsgQueue ( hmq ) ;
         WinTerminate ( hab ) ;
         return FALSE ;
     }

     hwndFrame = WinCreateStdWindow ( HWND_DESKTOP , ( ULONG ) NULL , & fcf ,
                                      pszClkName , pszClkName , WS_VISIBLE ,
                                      (HMODULE) NULL , ID_RESOURCE , & hwndCli

     if ( hwndFrame == NULL ) {
         WinDestroyMsgQueue ( hmq ) ;
         WinTerminate ( hab ) ;
         return FALSE ;
     }

     /* add ourselves to the switch list */
     WinQueryWindowProcess ( hwndFrame , & pid , & tid ) ;
     swctl . hwnd = hwndFrame ;
     swctl . idProcess = pid ;
     hsw = WinAddSwitchEntry ( & swctl ) ;

     return TRUE ;
 }


 COMPORT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\COMTALK\COMPORT.C

 /*
    comport.c -- This file contains the sources for COM port manipulation.
    Created by Microsoft Corporation, 1989
 */
 #define  INCL_DOSFILEMGR
 #define         INCL_DOSDEVICES
 #define         INCL_DOSDEVIOCTL
 #include <os2.h>
 #include "global.h"
 #include "comport.h"
 /*
    Constants
 */
        XON        0x11        /* Ctrl Q */
        XOFF        0x13        /* Ctrl S */
 char        CRLF[2] = { 0x0d, 0x0a };

 /*
     Variables
 */
 DCBINFO                dcbinfo;        /* Device control block for Ioctl 53H,
 HFILE                hPort;
 LINECONTROL        lnctlBuf;
 int                rc;
 USHORT                usErrWord;

 int ComFlush(void) {
 /*
     Flush the COM port with Category 11 functions
 */
     BYTE Data, Zero = 0;

     /* Call Category 11 Functions 1H, 2H  Flush Input, Output Buffers */
     if (rc = DosDevIOCtl(&Data, &Zero, 0x01, 11, hPort)) return rc;
     if (rc = DosDevIOCtl(&Data, &Zero, 0x02, 11, hPort)) return rc;
     return 0;
 }

 int ComInit(COM comTerm) {
 /*
     Open the COM port according to the specifications
 */
     USHORT action;

     /* Get File Handle for COM port (shared read/write access) */
     if (rc = DosOpen(comTerm.szPort,&hPort, &action, 0L, 0, 0x0001, 0x0042, 0
         return rc;

     /* Call Category 1 Function 41H   Set Baud Rate */
     if (rc = DosDevIOCtl(NULL, &comTerm.usBaud, 0x41, 1, hPort)) return rc;

     /* Call Category 1 Function 42H   Set Line Characteristics */
     lnctlBuf.bDataBits        = comTerm.bData;
     lnctlBuf.bParity        = comTerm.bParity;
     lnctlBuf.bStopBits        = (BYTE) (comTerm.bStop - 20);        /* IDD_ON
     if (rc = DosDevIOCtl(NULL, &lnctlBuf, 0x42, 1, hPort)) return rc;

     /* Call Category 1 Function 73H   Query Device Control Block */
     if (rc = DosDevIOCtl(&dcbinfo, 0L, 0x73, 1, hPort)) return rc;

     /*
         Do we want software handshaking?
     */
     dcbinfo.fbFlowReplace        &= ~(0x03);        /* Clear bits 0 and 1 */
     dcbinfo.fbFlowReplace        |=
         (comTerm.fSoftware)        ? (MODE_AUTO_TRANSMIT | MODE_AUTO_RECEIVE)
     /*
         Do we want hardware handshaking?
     */
     /* Turn on DTR, if appropriate */
     dcbinfo.fbCtlHndShake        &= ~(0x03);        /* Clear bits 0 and 1 */
     dcbinfo.fbCtlHndShake        |= ((comTerm.fHardware) ? MODE_DTR_CONTROL :

     /* Turn on RTS, if appropriate */
     dcbinfo.fbFlowReplace        &= ~(0xc0);        /* Clear bits 6 and 7 */
     dcbinfo.fbFlowReplace        |= ((comTerm.fHardware) ? MODE_RTS_CONTROL :

     /* Adjust CTS output handshaking */
     dcbinfo.fbCtlHndShake        &= ~MODE_CTS_HANDSHAKE;     /* Clear bit 3 *
     dcbinfo.fbCtlHndShake        |= ((comTerm.fHardware)?MODE_CTS_HANDSHAKE:0

     /* Adjust DSR output handshaking */
     dcbinfo.fbCtlHndShake        &= ~MODE_DSR_HANDSHAKE;     /* Clear bit 4 *
     dcbinfo.fbCtlHndShake        |= ((comTerm.fHardware)?MODE_DSR_HANDSHAKE:0

     /* Turn off DCD output handshaking */
     dcbinfo.fbCtlHndShake        &= ~MODE_DCD_HANDSHAKE;     /* Clear bit 5 *

     /* Adjust DSR input sensitivity */
     dcbinfo.fbCtlHndShake        &= ~MODE_DSR_SENSITIVITY;   /* Clear bit 6 *
     dcbinfo.fbCtlHndShake        |= ((comTerm.fHardware)?MODE_DSR_SENSITIVITY
     /*
         Set the line to Wait for Character, Read mode
     */
     dcbinfo.fbTimeout                &= ~(0x06);        /* Clear bits, then s
     dcbinfo.fbTimeout                |= MODE_WAIT_READ_TIMEOUT;
     dcbinfo.usReadTimeout        = -1;                /* Never! */

     /* Call Category 1 Function 53H   Set Device Control Block */
     if (rc = DosDevIOCtl(0L, &dcbinfo, 0x53, 1, hPort)) return rc;

     /* Get ready to start */
     return ComFlush();
 }

 USHORT ComRead(Line pli) {
 /*
     Reads all characters present
     Returns:        0 if successful
                 nonzero (Dos Error or Com Error Word) if unsuccessful
 */
     /* Read from the port... And snatch as many as you can! (blocking read) *
     if (rc = DosRead(hPort, pli->szText, MAXLINELEN, &(pli->cch))) return rc;

     /* Check the COM Error Word */
     if (rc = DosDevIOCtl(&usErrWord, NULL, 0x6d, 1, hPort)) return rc;

     /* ...then return it */
     return usErrWord;
 }

 int ComWrite(char ch) {
 /*
     Write a character at a time

     Okay as long as you don't type too fast
 */
     USHORT nCharsWritten;

     return DosWrite(hPort, &ch, 1, &nCharsWritten);
 }

 int ComClose(void) {
 /*
     Close the COM port
 */
     if (rc = ComFlush()) return rc;
     return DosClose(hPort);
 }

 int ComBreak(void) {
 /*
     Set BREAK mode ON
 */
     USHORT ComErr;

     /* Call Category 1 Function 4BH -- Set Break On */
     return DosDevIOCtl(&ComErr, NULL, 0x4b, 1, hPort);
 }

 int ComUnbreak(void) {
 /*
     Set BREAK mode OFF
 */
     USHORT ComErr;

     /* Call Category 1 Function 45H -- Set Break Off */
     return DosDevIOCtl(&ComErr, NULL, 0x45, 1, hPort);
 }

 int ComError(void) { return (int) usErrWord; }


 COMTALK.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\COMTALK\COMTALK.C

 /*
    comtalk.c -- Main routines
    Created by Microsoft Corporation, 1989

    This file contains the sources for the dialog box manipulation, and menu
    managment, and other aspects of interfacing with the user.
 */
 #define INCL_WIN
 #include <os2.h>
 "comtalk.h"        /* definition of COM from Global, and Resource IDs */
 "avio.h"        /* Routines needed to manage AVIO Presentation Space */
 "threads.h"        /* Thread initialization and control routines */
 <stdio.h>        /* Only needed for file I/O */
 <string.h>        /* one strcpy call */
 /*
     Variables
 */
 CHAR                 szCaption[] = "";
 HAB                hAB;
 COM                comTerm;
 COM                comTemp;
 HWND                hWndMenu;
 CLASSINFO        clsi;
 PFNWP                pfnOldFrameWndProc;
 BOOL                fConnected        = FALSE;
 BOOL                fPaging;
 int                iUpdate;
 BOOL                fFreeze                = TRUE;
 int                iError;
 /*
     Macros
 */
 #define InRange(x, a, b) ((x >= a) && (x <= b))

 /*
     Shorthand for sending messages, querying
 */
 #define Parent(h) \
     WinQueryWindow(h, QW_PARENT, FALSE)

 #define EnableMenuItem(id) \
     WinSendMsg(hWndMenu, MM_SETITEMATTR, MPFROM2SHORT(id, TRUE), \
                MPFROM2SHORT(MIA_DISABLED,0))

 #define DisableMenuItem(id) \
     WinSendMsg(hWndMenu, MM_SETITEMATTR, MPFROM2SHORT(id, TRUE), \
                MPFROM2SHORT(MIA_DISABLED, MIA_DISABLED))

 #define CheckMenuItem(id) \
     WinSendMsg(hWndMenu, MM_SETITEMATTR, MPFROM2SHORT(id, TRUE), \
                MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED))

 #define UnCheckMenuItem(id) \
     WinSendMsg(hWndMenu, MM_SETITEMATTR, MPFROM2SHORT(id, TRUE), \
                MPFROM2SHORT(MIA_CHECKED, 0))

 #define PushButton(h, id) \
     WinSendDlgItemMsg(h, id, BM_SETCHECK, MPFROM2SHORT(TRUE, 0), 0L)

 #define Valid(bData, bStop) \
     (((bData == IDD_FIVE) && (bStop != IDD_TWOSTOP)) \
     || ((bData != IDD_FIVE) && (bStop != IDD_ONEFIVE)))

 #define ErrMsg(h, s) \
     WinMessageBox(HWND_DESKTOP, h, s, NULL, 0, MB_OK | MB_ICONEXCLAMATION)

 char Ctrl(char ch) {
     return  (CHAR) ((('a' <= ch) && (ch <= 'z')) ?  (ch - 'a' + '\001') :
           ((('A' <= ch) && (ch <= 'Z')) ? (ch - 'A' + '\001') : ch));
 }

 /*
     Local/Private routines
 */
 void ReadOpts(HWND);
 void InitTerm(void);
 void Initialize(HWND);
 void ChangeSystemMenu(HWND);
 BOOL Filter(USHORT, char, USHORT);

 void main (void) {
      static CHAR szClientClass[] = "Terminal";
      HMQ        hmq;
      HWND        hWndClient, hWndFrame;
      QMSG        qmsg;
      ULONG        flFrameFlags = FCF_STANDARD | FCF_HORZSCROLL | FCF_VERTSCRO
      ULONG         flFrameStyle = WS_VISIBLE | FS_SCREENALIGN;

      hAB = WinInitialize(0);
      hmq = WinCreateMsgQueue(hAB, 0);

      WinRegisterClass(hAB, szClientClass, ClientWndProc, CS_SYNCPAINT, 0);

      hWndFrame = WinCreateStdWindow(HWND_DESKTOP, flFrameStyle,
                                     &flFrameFlags, szClientClass, szCaption,
                                      0L, (HMODULE) NULL, ID_RESOURCE, &hWndCl

      /* Setup AVIO PS and force a paint */
      AvioInit(hWndFrame, hWndClient);
      WinSendMsg(hWndClient, WM_PAINT, NULL, NULL);

      /* Try to subclass the Frame window... */
      pfnOldFrameWndProc = WinSubclassWindow(hWndFrame, NewFrameWndProc);

      while (WinGetMsg(hAB, &qmsg, NULL, 0, 0)) WinDispatchMsg(hAB, &qmsg);

      /* Blast the AVIO PS */
      AvioClose();

      WinDestroyWindow(hWndFrame);
      WinDestroyMsgQueue(hmq);
      WinTerminate(hAB);
      DosExit(EXIT_PROCESS, 0);
 }

 MRESULT CALLBACK ClientWndProc(HWND hWnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 /*
      Window Procedure which traps messages to the Client area
 */
      switch (msg) {
           case WM_AVIOUPDATE:
                 fNoUpdate = AvioUpdateLines(FALSE, &fPaging);
                 if (fConnected && fPaging) {
                     CheckMenuItem(IDM_PAGING);
                 }
                 break;

           case WM_MSGBOX:
                 iUpdate = (int)SHORT1FROMMP( mp2);
                 switch ((int)SHORT1FROMMP(mp1)) {
                     case (int) MBE_COMREAD:
                         if (iError = iUpdate) EnableMenuItem(IDM_ERRORS);
                         iUpdate = 0;
                         break;

                     default:
                         ErrMsg(hWnd, aszMessage[SHORT1FROMMP(mp1)]);
                         break;
                 }
                 if (iUpdate) {        /* Page down because queue is full */
                     fNoUpdate = AvioUpdateLines(TRUE, &fPaging);
                     if (fConnected && fPaging) CheckMenuItem(IDM_PAGING);
                     else UnCheckMenuItem(IDM_PAGING);
                     ThdReset();
                 }
                 break;

           case WM_CREATE:
                 ChangeSystemMenu(hWnd);
                 /*
                     Initialize the Dialog Options
                 */
                 Initialize(hWnd);
                 /*
                     Get the Handle so you can enable/disable menu items
                     Thanks again to Charles Petzold
                 */
                 hWndMenu = WinWindowFromID(Parent(hWnd), FID_MENU);
                 /*
                     Disable some entries (can do this in the resource file)
                 */
                 DisableMenuItem(IDM_CLOSE);
                 DisableMenuItem(IDM_BREAK);
                 DisableMenuItem(IDM_COMMANDMENU);
                 break;

           case WM_PAINT:                /* Paint the AVIO way! */
                 AvioPaint(hWnd);
                 break;

           case WM_SIZE:                        /* Size the AVIO way!  */
                 fNoUpdate = AvioUpdateLines(FALSE, &fPaging);
                 if (fConnected && fPaging) {
                     CheckMenuItem(IDM_PAGING);
                 }
                 return AvioSize(hWnd, msg, mp1, mp2);
                 break;

           case WM_HSCROLL:
                 AvioScroll(HIUSHORT(mp2), LOUSHORT(mp2), TRUE);
                 break;

           case WM_VSCROLL:
                 AvioScroll(HIUSHORT(mp2), LOUSHORT(mp2), FALSE);
                 break;

           case WM_ERASEBACKGROUND:
                 return 0;
                 break;

           case WM_COMMAND:
                 switch (COMMANDMSG(&msg)->cmd) {
                     case IDM_ABOUT:
                         WinDlgBox(HWND_DESKTOP, hWnd, AboutDlgProc,
                                   (HMODULE) NULL, IDD_ABOUT, NULL);
                         return 0;

                     case IDM_HELP:
                         WinDlgBox(HWND_DESKTOP, hWnd, AboutDlgProc,
                                   (HMODULE) NULL, IDD_MAINHELPBOX, NULL);
                         return 0;

                     case IDM_SETTINGS:
                         WinDlgBox(HWND_DESKTOP, hWnd, SetDlgProc,
                                   (HMODULE) NULL, IDD_SET, NULL);
                         return 0;

                     case IDM_CONNECT:
                         AvioStartup(hWnd);
                         ThdInitialize(hWnd, comTerm);        /* Spawn 3 threa
                         /*
                             Disable/Enable Menu Items
                         */
                         DisableMenuItem(IDM_CONNECT);
                         DisableMenuItem(IDM_SETTINGS);
                         DisableMenuItem(IDM_ERRORS);

                         EnableMenuItem(IDM_CLOSE);
                         EnableMenuItem(IDM_BREAK);
                         EnableMenuItem(IDM_COMMANDMENU);
                         fConnected = TRUE;
                         return 0;

                     case IDM_CLOSE:
                         fConnected = FALSE;
                         ThdTerminate();                /* Might have to wait?
                         /*
                             Update menu items
                         */
                         UnCheckMenuItem(IDM_BREAK);

                         DisableMenuItem(IDM_CLOSE);
                         DisableMenuItem(IDM_BREAK);
                         DisableMenuItem(IDM_COMMANDMENU);

                         EnableMenuItem(IDM_CONNECT);
                         EnableMenuItem(IDM_SETTINGS);

                         return 0;

                     case IDM_BREAK:
                         ThdDoBreak();
                         return 0;

                     case IDM_ERRORS:
                         if (iError & 1)
                             ErrMsg(hWnd, "Receive Queue Overrun");
                         if (iError & 2)
                             ErrMsg(hWnd, "Receive Hardware Overrun");
                         if (iError & 4)
                             ErrMsg(hWnd, "Parity Error");
                         if (iError & 8)
                             ErrMsg(hWnd, "Framing Error");
                         DisableMenuItem(IDM_ERRORS);
                         return 0;

                     case IDM_PAGE:
                         fNoUpdate = AvioUpdateLines(TRUE, &fPaging);
                         if (fPaging) CheckMenuItem(IDM_PAGING);
                         else UnCheckMenuItem(IDM_PAGING);
                         return 0;

                     case IDM_UP:
                         AvioPageUp();
                         return 0;

                     case IDM_PAGING:
                         if (fPaging = !fPaging) {
                             CheckMenuItem(IDM_PAGING);
                         } else {
                             UnCheckMenuItem(IDM_PAGING);
                         }
                         return 0;

                     default: return 0;
                 }

           case WM_CHAR:                /* Put characters in typeahead buffer
                 if (fConnected && !(CHARMSG(&msg)->fs & KC_KEYUP))
                     if (Filter( CHARMSG(&msg)->fs,
                          (char)        CHARMSG(&msg)->chr,
                                 CHARMSG(&msg)->vkey))
                         ErrMsg(hWnd, "Error Writing COM Port");
                 break;

           case WM_TRACKFRAME:
                 AvioTrackFrame(hWnd, mp1);
                 break;

           case WM_MINMAXFRAME: /* Trap MAXIMIZE messages */
                 AvioMinMax((PSWP) mp1);

           default: return WinDefWindowProc(hWnd, msg, mp1, mp2);
      }
      return 0;
 }

 MRESULT CALLBACK AboutDlgProc(HWND hDlg, USHORT msg, MPARAM mp1, MPARAM mp2)
 /*
     Dialog box control for the ABOUT COMTALK... dialog box
 */
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 void WriteOpts(void) {
 /*
     Write Settings to file COMTALK.INI
 */
     FILE *fp;

     fp = fopen("comtalk.ini", "w+");
     fprintf(fp, "%d %d %d %d %d %d %d %s\n", comTerm.usBaud, comTerm.bParity,
                 comTerm.bData, comTerm.bStop, comTerm.fWrap,
                 comTerm.fHardware, comTerm.fSoftware, comTerm.szPort);
     fclose(fp);
 }

 void ReadOpts(HWND hWnd) {
 /*
     Read Settings from COMTALK.INI
 */
     FILE *fp;

     /* Use InitTerm() if we have reading problems */
     if ((fp = fopen("comtalk.ini", "r")) == NULL) InitTerm();
     else if (fscanf(fp, "%d%d%d%d%d%d%d%s", &comTerm.usBaud, &comTerm.bParity
         &comTerm.bData, &comTerm.bStop, &comTerm.fWrap,
         &comTerm.fHardware, &comTerm.fSoftware, comTerm.szPort) == EOF)
         InitTerm();
     if (!Valid(comTerm.bData, comTerm.bStop)) {
         ErrMsg(hWnd, "Invalid terminal setting");
         InitTerm();
     }
     fclose(fp);
 }

 void InitTerm(void) {
 /*
     Initialize the TERM structure to DosDevIOCtl defaults
 */
     strcpy(comTerm.szPort, "com1");
     comTerm.usBaud = 9600; comTerm.bParity = IDD_EVENP;
     comTerm.bData = IDD_SEVEN; comTerm.bStop = IDD_ONESTOP;
     comTerm.fWrap = comTerm.fSoftware = TRUE; comTerm.fHardware = FALSE;
 }

 MRESULT CALLBACK SetDlgProc(HWND hDlg, USHORT msg, MPARAM mp1, MPARAM mp2) {
 /*
     The Settings Dialog Box control routine
 */
     BOOL        rc;
     BYTE        bTemp;

     switch(msg) {
         case WM_INITDLG:
             WinSetDlgItemText(hDlg, IDD_PORT, comTerm.szPort);
             WinSetDlgItemShort(hDlg, IDD_BAUD, comTerm.usBaud, FALSE);

             PushButton(hDlg, comTerm.bParity);
             PushButton(hDlg, comTerm.bData);
             PushButton(hDlg, comTerm.bStop);
             if (comTerm.fWrap) PushButton(hDlg, IDD_WRAP);
             if (comTerm.fHardware) PushButton(hDlg, IDD_HW);
             if (comTerm.fSoftware) PushButton(hDlg, IDD_SW);

             comTemp.bParity        = comTerm.bParity;
             comTemp.bData        = comTerm.bData;
             comTemp.bStop        = comTerm.bStop;
             comTemp.fWrap        = comTerm.fWrap;
             comTemp.fHardware        = comTerm.fHardware;
             comTemp.fSoftware   = comTerm.fSoftware;
             break;

         case WM_HELP:
             WinDlgBox(HWND_DESKTOP, hDlg, AboutDlgProc,
                       (HMODULE) NULL, IDD_SETHELPBOX, NULL);
             break;

         case WM_CONTROL:
             /*
                 The fact that these are AutoRadioButtons makes life easy.
             */
             bTemp = (BYTE) SHORT1FROMMP(mp1);        /* Which button pushed?
             if InRange(bTemp, IDD_NOP, IDD_SPACEP)
                  {
                         comTemp.bParity = bTemp;
             }
                 else
                 {
                         if InRange(bTemp, IDD_FIVE, IDD_EIGHT)
                         {
                                 comTemp.bData = bTemp;
                     }
                         else
                         {
                                 if InRange(bTemp, IDD_ONESTOP, IDD_TWOSTOP)
                                 {
                                         comTemp.bStop = bTemp;
                                 }
                              else
                                 {
                                         switch (bTemp) {
                                         case IDD_WRAP: comTemp.fWrap     = !c
                                         case IDD_HW  : comTemp.fHardware = !c
                                         case IDD_SW  : comTemp.fSoftware = !c
                                         default:
                                     }
                                 }
                         }
                 }
             break;
         case WM_COMMAND:        /* Ready to exit... */
             switch(COMMANDMSG(&msg)->cmd) {
                 case IDD_SAVE:
                 case DID_OK:
                     if (!Valid(comTemp.bData, comTemp.bStop)) {
                         ErrMsg(hDlg,"Data and Stop Bits Incompatible");
                         break;        /* No-op...Dialog not dismissed */
                     }
                     WinQueryDlgItemText(hDlg, IDD_PORT, 5, comTerm.szPort);
                     WinQueryDlgItemShort(hDlg, IDD_BAUD, &comTerm.usBaud, rc)
                     comTerm.bParity        = comTemp.bParity;
                     comTerm.bData        = comTemp.bData;
                     comTerm.bStop        = comTemp.bStop;
                     comTerm.fWrap        = comTemp.fWrap;
                     comTerm.fHardware        = comTemp.fHardware;
                     comTerm.fSoftware        = comTemp.fSoftware;
                     if (COMMANDMSG(&msg)->cmd == IDD_SAVE) WriteOpts();
                 case DID_CANCEL: WinDismissDlg(hDlg, FALSE);
                 default: break;
             }
             break;
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }

 void Initialize(HWND hWnd) {
     ReadOpts(hWnd);
     fPaging = FALSE;
 }

 void ChangeSystemMenu(HWND hWnd) {
 /*
     Insert items into the System Menu (with thanks to Charles Petzold)
 */
     static CHAR *x[2] = { NULL, "~About ComTalk..." }; /* Items to add */
     static MENUITEM mi[2] = {        /* The RESOURCE definitions */
         MIT_END, MIS_SEPARATOR, 0x0000, 0, NULL, 0,
         MIT_END, MIS_TEXT, 0x0000, IDM_ABOUT, NULL, 0
     };
     HWND        hSM, hSSM;        /* Menu and submenu handles */
     MENUITEM        miSM;                /* System Menu Menuitem     */
     SHORT        idSM;                /* ID of the System Menu    */
     /*
         Get ahold of the system menu
     */
     hSM = WinWindowFromID(Parent(hWnd), FID_SYSMENU);
     idSM = SHORT1FROMMR( WinSendMsg(hSM, MM_ITEMIDFROMPOSITION, NULL, NULL));
     WinSendMsg(hSM, MM_QUERYITEM, MPFROM2SHORT(idSM, FALSE), MPFROMP(&miSM));
     /*
         Manipulate the System SubMenu
     */
     hSSM = miSM.hwndSubMenu;
     WinSendMsg(hSSM, MM_INSERTITEM, MPFROMP(mi), MPFROMP(x[0]));
     WinSendMsg(hSSM, MM_INSERTITEM, MPFROMP(mi+1), MPFROMP(x[1]));
 }

 MRESULT CALLBACK NewFrameWndProc(HWND hWnd, USHORT msg, MPARAM mp1, MPARAM mp
 /*
     Force the frame to stay small enough
 */
     BOOL rc;                /* Return code for WM_QueryTrackInfo */

     switch(msg) {
         case WM_ADJUSTWINDOWPOS:        /* Calculate, then show scrollbars */
             AvioAdjustFrame(mp1);
             break;
         case WM_QUERYTRACKINFO:
             rc = (BOOL)SHORT1FROMMR ((*pfnOldFrameWndProc)(hWnd, msg, mp1, mp
             AvioQueryTrackInfo((PTRACKINFO) mp2);
             return (MRESULT) rc;
         default: break;
     }
     return (*pfnOldFrameWndProc)(hWnd, msg, mp1, mp2);
 }

 BOOL Filter(USHORT fs, char ch, USHORT vkey) {
     BOOL rc = FALSE;

     if (fs & KC_VIRTUALKEY) {
         switch(vkey) {
             case VK_HOME:
                 if (fs & KC_CTRL) rc = ThdPutString("\033[2J",4);
                 return (rc || ThdPutString("\033[H", 3));
             case VK_UP:
                 return ThdPutString("\033[A", 3);
             case VK_DOWN:
                 return ThdPutString("\033[B", 3);
             case VK_RIGHT:
                 return ThdPutString("\033[C", 3);
             case VK_LEFT:
                 return ThdPutString("\033[D", 3);
             default: break;
         }
     }

     if (fs & KC_CTRL) {
         switch (ch) {
             case 'l':
             case 'L': AvioRedraw();
             case '\0': return FALSE; break;
             default: ch = Ctrl(ch); break;
         }
     } else {
         switch (ch) {
             case '\0': return FALSE; break;
             default: break;
         }
     }
     return(rc || ThdPutChar(ch));
 }


 CPGREP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CPGREP\CPGREP.C

 /* cpgrep - string searches
  *
  * Created by Microsoft Corp. 1986
  *
  *
  */

 #include                <os2def.h>
 #define INCL_DOSPROCESS
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSQUEUES
 #define INCL_DOSMEMMGR
 #define INCL_DOSMISC
 #include                <bsedos.h>
 #include                <stdio.h>
 #include                <fcntl.h>
 #include                <ctype.h>

 #define        BEGLINE                0x40
 #define        DEBUG                0x08
 #define        ENDLINE                0x80
 #define        FILBUFLEN        (SECTORLEN*30)
 #define        FILNAMLEN        80
 #define        INVERT                0x10
 #define        ISCOT                0x0002
 #define        LG2SECLEN        9
 #define        LINELEN                128
 #define        LINENOS                0x04
 #define        LNOLEN                8
 #define        MAXSTRLEN        128
 #define        NAMEONLY        0x02
 #define        OUTBUFLEN        (SECTORLEN*4)
 #define        SECTORLEN        (1 << LG2SECLEN)
 #define        SHOWNAME        0x01
 #define        STKLEN                256
 #define        TIMER                0x20
 #define        TRTABLEN        256
 #define        s_text(x)        (((char *)(x)) - ((x)->s_must))

 typedef struct stringnode
   {
     struct stringnode        *s_alt;                /* List of alternates */
     struct stringnode        *s_suf;                /* List of suffixes */
     int                        s_must;                /* Length of portion th
   }
                         STRINGNODE;

 char                        filbuf[(FILBUFLEN + 2)*2];
 char                        *bufptr[] = { filbuf, filbuf + FILBUFLEN + 2 };
 char                        outbuf[OUTBUFLEN*2];
 char                        *obuf[] = { outbuf, outbuf + OUTBUFLEN };
 char                        *optr[] = { outbuf, outbuf + OUTBUFLEN };
 USHORT                        ocnt[] = { OUTBUFLEN, OUTBUFLEN };
 int                        oi = 0;
 char                        transtab[TRTABLEN] = { 0 };
 STRINGNODE                *stringlist[TRTABLEN/2];
 USHORT                        arrc;                /* I/O return code for Dos
 USHORT                        awrc;                /* I/O return code for Dos
 int                        casesen = 1;        /* Assume case-sensitivity */
 USHORT                        cbread;         /* Bytes read by DosRead */
 USHORT                        cbwrite;        /* Bytes written by DosWrite */
 int                        clists = 1;        /* One is first available index
 int                        flags;                /* Flags */
 int                        lineno;                /* Current line number */
 char                        pmode;                /* Protected mode flag */
 LONG                        readdone;        /* Async read done semaphore */
 LONG                        readpending;        /* Async read pending semapho
 int                        status = 1;        /* Assume failure */
 char                        *t2buf;                /* Async read buffer */
 USHORT                        t2buflen;        /* Async read buffer length */
 HFILE                        t2fd;                /* Async read file */
 char                        *t3buf;                /* Async write buffer */
 USHORT                        t3buflen;        /* Async write buffer length *
 HFILE                        t3fd;                /* Async write file */
 LONG                        writedone;        /* Async write done semaphore *
 LONG                        writepending;        /* Async write pending semap
 char                        target[MAXSTRLEN];
                                         /* Last string added */
 int                        targetlen;        /* Length of last string added *

 int                        countlines();        /* See CPGREPSB.ASM */
 int                        countmatched();        /* See CPGREPSB.ASM */
 char                        *findlist();        /* See CPGREPSB.ASM */
 char                        *findone();        /* See CPGREPSB.ASM */
 void                        flush1buf();        /* See below */
 int                        grepbuffer();        /* See below */
 int                        revfind();        /* See CPGREPSB.ASM */
 void                        write1buf();        /* See below */
 char                        *(*find)() = findlist;
 void                        (*flush1)() = flush1buf;
 int                        (*grep)() = grepbuffer;
 void                        (*write1)() = write1buf;

 void                        freenode(x)
 register STRINGNODE        *x;                /* Pointer to node to free */
   {
     register STRINGNODE        *y;                /* Pointer to next node in

     while(x != NULL)                        /* While not at end of list */
       {
         if(x->s_suf != NULL) freenode(x->s_suf);
                                         /* Free suffix list */
         y = x;                                /* Save pointer */
         x = x->s_alt;                        /* Move down the list */
         free(y);                        /* Free the node */
       }
   }


 STRINGNODE                *newnode(s,n)
 char                        *s;                /* String */
 int                        n;                /* Length of string */
   {
     register STRINGNODE        *new;                /* Pointer to new node */
     char                *t;                /* String pointer */
     char                *malloc();        /* Storage allocator */

     if((t = malloc(sizeof(STRINGNODE) + n + (n & 1))) == NULL)
       {                                        /* If allocation fails */
         fprintf(stderr,"Out of memory\n");
         DosExit( EXIT_PROCESS, 2);        /* Print error message and die */
       }
     if(n & 1) ++t;                        /* END of string word-aligned */
     strncpy(t,s,n);                        /* Copy string text */
     new = (STRINGNODE *)(t + n);        /* Set pointer to node */
     new->s_alt = NULL;                        /* No alternates yet */
     new->s_suf = NULL;                        /* No suffixes yet */
     new->s_must = n;                        /* Set string length */
     return(new);                        /* Return pointer to new node */
   }


 void                        reallocnode(node,s,n)
 register STRINGNODE        *node;                /* Pointer to node */
 char                        *s;                /* String */
 register int                n;                /* Length of string */
   {
     if(n > node->s_must)                /* If node must grow */
       {
         fprintf(stderr,"Internal error\n");
                                         /* Error message */
         DosExit( EXIT_PROCESS, 2);                          /* Error exit */
       }
     node->s_must = n;                        /* Set new length */
     memcpy(s_text(node),s,n);                /* Copy new text */
   }


 void                        addstring(s,n)
 char                        *s;                /* String to add */
 int                        n;                /* Length of string */
   {
     register STRINGNODE        *cur;                /* Current string */
     register STRINGNODE        **pprev;        /* Pointer to previous link */
     STRINGNODE                *new;                /* New string */
     int                        i;                /* Index */
     int                        j;                /* Count */
     int                        k;                /* Count */

     if(n <= 0 || n > 127) return;        /* Should never happen */
     i = transtab[*s];                        /* Get current index */
     if(i == 0)                                /* If no existing list */
       {
         /*
          *  We have to start a new list
          */
         if((i = clists++) >= TRTABLEN/2)
           {                                /* If too many string lists */
             fprintf(stderr,"Too many string lists\n");
                                         /* Error message */
             DosExit( EXIT_PROCESS, 2);        /* Die */
           }
         stringlist[i] = NULL;                /* Initialize */
         transtab[*s] = i;                /* Set pointer to new list */
         if(!casesen && isalpha(*s)) transtab[*s ^ '\x20'] = i;
                                         /* Set pointer for other case */
       }
     else if(stringlist[i] == NULL) return;
                                         /* Check for existing 1-byte string *
     if(--n == 0)                        /* If 1-byte string */
       {
         freenode(stringlist[i]);        /* Free any existing stuff */
         stringlist[i] = NULL;                /* No record here */
         return;                                /* Done */
       }
     ++s;                                /* Skip first char */
     pprev = stringlist + i;                /* Get pointer to link */
     cur = *pprev;                        /* Get pointer to node */
     while(cur != NULL)                        /* Loop to traverse match tree
       {
         i = (n > cur->s_must)? cur->s_must: n;
                                         /* Find minimum of string lengths */
         matchstrings(s,s_text(cur),i,&j,&k);
                                         /* Compare the strings */
         if(j == 0)                        /* If complete mismatch */
           {
             if(k < 0) break;                /* Break if insertion point found
             pprev = &(cur->s_alt);        /* Get pointer to alternate link */
             cur = *pprev;                /* Follow the link */
           }
         else if(i == j)                        /* Else if strings matched */
           {
             if(i == n)                        /* If new is prefix of current
               {
                 reallocnode(cur,s_text(cur),n);
                                         /* Shorten text of node */
                 if(cur->s_suf != NULL)        /* If there are suffixes */
                   {
                     freenode(cur->s_suf);
                                         /* Suffixes no longer needed */
                     cur->s_suf = NULL;
                   }
                 return;                        /* All done */
               }
             pprev = &(cur->s_suf);        /* Get pointer to suffix link */
             if((cur = *pprev) == NULL) return;
                                         /* Done if current is prefix of new *
             s += i;                        /* Skip matched portion */
             n -= i;
           }
         else                                /* Else partial match */
           {
             /*
              *        We must split an existing node.
              *        This is the trickiest case.
              */
             new = newnode(s_text(cur) + j,cur->s_must - j);
                                         /* Unmatched part of current string *
             reallocnode(cur,s_text(cur),j);
                                         /* Set length to matched portion */
             new->s_suf = cur->s_suf;        /* Current string's suffixes */
             if(k < 0)                        /* If new preceded current */
               {
                 cur->s_suf = newnode(s + j,n - j);
                                         /* FIrst suffix is new string */
                 cur->s_suf->s_alt = new;/* Alternate is part of current */
               }
             else                        /* Else new followed current */
               {
                 new->s_alt = newnode(s + j,n - j);
                                         /* Unmatched new string is alternate
                 cur->s_suf = new;        /* New suffix list */
               }
             return;
           }
       }
     *pprev = newnode(s,n);                /* Set pointer to new node */
     (*pprev)->s_alt = cur;                /* Attach alternates */
   }


 int                        addfancy(buffer,buflen,seplist)
 register char                *buffer;        /* Buffer */
 int                        buflen;                /* Length of buffer */
 char                        *seplist;        /* List of separators */
   {
     register char        *bufend;        /* Pointer to end of buffer */
     int                        strcnt = 0;        /* String count */
     int                        len;                /* String length */
     char                c;                /* One char buffer */

     bufend = buffer + buflen;                /* Set end pointer */
     while(buffer < bufend)                /* Loop through all strings */
       {
         len = strncspn(buffer,seplist,bufend - buffer);
                                         /* Length of string */
         if(flags & ENDLINE)                /* If match must be at end of line
           {
             c = buffer[len];                /* Save 1st character past string
             buffer[len++] = '\r';        /* Carriage return marks end of line
           }
         if(findlist(buffer,buffer + len) == NULL)
           {                                /* If no match within string */
             addstring(buffer,len);        /* Add string to list */
             if(strcnt++ == 0)                /* If first string */
               {
                 memcpy(target,buffer,len);
                                         /* Save first string in buffer */
                 targetlen = len;        /* Remember length */
               }
           }
         buffer += len;                        /* Skip over string */
         if(flags & ENDLINE) (--buffer)[0] = c;
                                         /* Restore saved character */
         buffer += strnspn(buffer,seplist,bufend - buffer);
                                         /* Skip over trailing separators */
       }
     return(strcnt);                        /* Return string count */
   }


 int                        addplain(buffer,buflen,seplist)
 register char                *buffer;        /* String list buffer */
 int                        buflen;                /* Buffer length */
 char                        *seplist;        /* List of separators */
   {
     int                        strcnt;                /* String count */
     register int        len;                /* String length */
     char                c;                /* One char buffer */

     strcnt = 0;
     while((len = strncspn(buffer,seplist,buflen)) > 0)
       {                                        /* While not at end of input l
         if(flags & ENDLINE)                /* If match must be at end of line
           {
             c = buffer[len];                /* Save 1st character past string
             buffer[len++] = '\r';        /* Carriage return marks end of line
           }
         if(strcnt == 0)                        /* Save first string */
           {
             strncpy(target,buffer,len);        /* Save string in buffer */
             targetlen = len;                /* Save string length */
           }
         addstring(buffer,len);                /* Add the string to the table
         if(flags & ENDLINE) buffer[--len] = c;
                                         /* Restore saved character */
         buffer += len;                        /* Skip the string */
         buflen -= len;
         len = strnspn(buffer,seplist,buflen);
                                         /* Skip separators */
         buffer += len;
         buflen -= len;
         ++strcnt;                        /* Increment string count */
       }
     return(strcnt);                        /* Return string count */
   }


 void                        dumplist(node,indent)
 register STRINGNODE        *node;                /* Pointer to list to dump *
 int                        indent;                /* Current length of buffer
   {
     int                        i;                /* Counter */

     while(node != NULL)                        /* While not at end of list */
       {
         for(i = 0; i < indent; ++i) fputc(' ',stderr);
         fwrite(s_text(node),sizeof(char),node->s_must,stderr);
         fprintf(stderr,"\n");
         if(node->s_suf != NULL)
           dumplist(node->s_suf,indent + node->s_must);
                                         /* Recurse to do suffixes */
         node = node->s_alt;                /* Do next alternate in list */
       }
   }


 void                        dumpstrings()
   {
     int                        i;                /* Index */

     for(i = 0; i < TRTABLEN; ++i)        /* Loop through translation table */
       {
         if(transtab[i] == 0) continue;        /* Skip null entries */
         fprintf(stderr,"%c\n",i);        /* Print the first byte */
         dumplist(stringlist[transtab[i]],1);
                                         /* Dump the list */
       }
   }


 HFILE                        openfile(name)
 char                        *name;                /* File name */
   {
     HFILE                fd;                /* File descriptor */

     if((fd = open(name,0)) == -1)        /* If error opening file */
       {
         fprintf(stderr,"Cannot open %s\r\n",name);
                                         /* Print error message */
       }
     return(fd);                                /* Return file descriptor */
   }


 void far                thread2()        /* Read thread */
   {
     while(DosSemRequest( &readpending, -1L) == 0)
       {                                        /* While there is work to do *
         arrc = DosRead( t2fd, t2buf, t2buflen, &cbread);
                                         /* Do the read */
         DosSemClear( &readdone);        /* Signal read completed */
       }
     fprintf(stderr,"Thread 2: DosSemRequest failed\n");
                                         /* Print error message */
     DosExit( EXIT_PROCESS, 2);                /* Die */
   }


 void far                thread3()        /* Write thread */
   {
     while(DosSemRequest((long far *) &writepending,-1L) == 0)
       {                                        /* While there is work to do *
         awrc = DosWrite(t3fd, t3buf, t3buflen, &cbwrite);
                                         /* Do the write */
         DosSemClear( &writedone);        /* Signal write completed */
       }
     fprintf(stderr,"Thread 3: DosSemRequest failed\n");
                                         /* Print error message */
     DosExit( EXIT_PROCESS, 2);                /* Die */
   }


 void                        startread(fd,buffer,buflen)
 HFILE                        fd;                /* File handle */
 char                        *buffer;        /* Buffer */
 USHORT                        buflen;         /* Buffer length */
   {
     if(pmode)                                /* If protected mode */
       {
         if(DosSemRequest( &readdone, -1L) != 0)
           {                                /* If we fail to get the semaphore
             fprintf(stderr,"DosSemRequest failed\n");
                                         /* Error message */
             DosExit( EXIT_PROCESS, 2);        /* Die */
           }
         t2fd = fd;                        /* Set parameters for read */
         t2buf = buffer;
         t2buflen = buflen;
         DosSemClear( &readpending);        /* Wake thread 2 for read */
         DosSleep(0L);                        /* Yield the CPU */
       }
     else arrc = DosRead( fd, buffer, buflen, &cbread);
   }


 int                        finishread()
   {
     if(pmode && DosSemWait( &readdone, -1L) != 0)
       {                                        /* If protected mode and wait
         fprintf(stderr,"DosSemWait failed\n");
                                         /* Print error message */
         DosExit( EXIT_PROCESS, 2);                          /* Die */
       }
     return((arrc == 0)? cbread: -1);        /* Return number of bytes read */
   }


 void                        startwrite(fd,buffer,buflen)
 HFILE                        fd;                /* File handle */
 char                        *buffer;        /* Buffer */
 USHORT                        buflen;         /* Buffer length */
   {
     if(pmode)                                /* If protected mode */
       {
         if(DosSemRequest( &writedone, -1L) != 0)
           {                                /* If we fail to get the semaphore
             fprintf(stderr,"DosSemRequest failed\n");
                                         /* Error message */
             DosExit( EXIT_PROCESS, 2);        /* Die */
           }
         t3fd = fd;                        /* Set parameters for write */
         t3buf = buffer;
         t3buflen = buflen;
         DosSemClear( &writepending);        /* Wake thread 3 for read */
         DosSleep(0L);                        /* Yield the CPU */
       }
     else awrc = DosWrite(fd, buffer, buflen, &cbwrite);
   }


 int                        finishwrite()
   {
     if(pmode && DosSemWait( &writedone, -1L) != 0)
       {                                        /* If protected mode and wait
         fprintf(stderr,"DosSemWait failed\n");
                                         /* Print error message */
         DosExit( EXIT_PROCESS, 2);        /* Die */
       }
     return((awrc == 0)? cbwrite: -1);        /* Return number of bytes writte
   }


 void                        write1nobuf(buffer,buflen)
 char                        *buffer;        /* Buffer */
 USHORT                        buflen;         /* Buffer length */
   {
     int                        cb;                /* Count of bytes written *

     if( DosWrite(1, buffer, buflen, &cb) != 0 || cb != buflen)
                                         /* If write fails */
       {
         fprintf(stderr,"write error %d\n",awrc);
                                         /* Print error message */
         DosExit( EXIT_PROCESS, 2);        /* Die */
       }
   }


 void                        write1buf(buffer,buflen)
 char                        *buffer;        /* Buffer */
 USHORT                        buflen;         /* Buffer length */
   {
     USHORT                cb;                /* Byte count */

     while(buflen > 0)                        /* While bytes remain */
       {
         if(awrc != 0)                        /* If previous write failed */
           {
             fprintf(stderr,"write error %d\n",awrc);
                                         /* Print error message */
             DosExit( EXIT_PROCESS, 2);                          /* Die */
           }
         if((cb = ocnt[oi]) == 0)        /* If buffer full */
           {
             startwrite(1,obuf[oi],OUTBUFLEN);
                                         /* Write the buffer */
             ocnt[oi] = OUTBUFLEN;        /* Reset count and pointer */
             optr[oi] = obuf[oi];
             oi ^= 1;                        /* Switch buffers */
             cb = ocnt[oi];                /* Get space remaining */
           }
         if(cb > buflen) cb = buflen;        /* Get minimum */
         memcpy(optr[oi],buffer,cb);        /* Copy bytes to buffer */
         ocnt[oi] -= cb;                        /* Update buffer length and po
         optr[oi] += cb;
         buflen -= cb;
         buffer += cb;
       }
   }


 void                        flush1nobuf()
   {
   }


 void                        flush1buf()
   {
     int                        cb;                /* Byte count */

     if((cb = OUTBUFLEN - ocnt[oi]) > 0)        /* If buffer not empty */
       {
         startwrite(1,obuf[oi],cb);        /* Start write */
         if(finishwrite() != cb)                /* If write failed */
           {
             fprintf(stderr,"write error %d\n",awrc);
                                         /* Print error message */
             DosExit( EXIT_PROCESS, 2);        /* Die */
           }
       }
   }


 int                        grepnull(cp,endbuf,name)
 register char                *cp;                /* Buffer pointer */
 char                        *endbuf;        /* End of buffer */
 char                        *name;                /* File name */
   {
     return(0);                                /* Do nothing */
   }


 int                        grepbuffer(startbuf,endbuf,name)
 char                        *startbuf;        /* Start of buffer */
 char                        *endbuf;        /* End of buffer */
 char                        *name;                /* File name */
   {
     register char        *cp;                /* Buffer pointer */
     char                *lastmatch;        /* Last matching line */
     int                        linelen;        /* Line length */
     int                        namlen = 0;        /* Length of name */
     char                lnobuf[LNOLEN];        /* Line number buffer */
     char                nambuf[LINELEN];/* Name buffer */

     cp = startbuf;                        /* Initialize to start of buffer */
     lastmatch = cp;                        /* No previous match yet */
     while((cp = (*find)(cp,endbuf)) != NULL)
       {                                        /* While matches are found */
         if((flags & BEGLINE) && cp[-1] != '\n' && cp > startbuf)
           {                                /* If begin line conditions not me
             ++cp;                        /* Skip first char of match */
             continue;                        /* Keep looking */
           }
         status = 0;                        /* Match found */
         if(flags & NAMEONLY)                /* If filename only wanted */
           {
             (*write1)(nambuf,sprintf(nambuf,"%s\r\n",name));
                                         /* Print the name */
             return(1);                        /* Punt remainder of buffer */
           }
         cp -= revfind(cp,'\n',cp - startbuf);
                                         /* Point at last linefeed */
         if(*cp == '\n') ++cp;                /* Point at start of line */
         if(flags & SHOWNAME)                /* If name wanted */
           {
             if(namlen == 0) namlen = sprintf(nambuf,"%s:",name);
                                         /* Format name if not done already */
             (*write1)(nambuf,namlen);        /* Show name */
           }
         if(flags & LINENOS)                /* If line number wanted */
           {
             lineno += countlines(lastmatch,cp);
                                         /* Count lines since last match */
             (*write1)(lnobuf,sprintf(lnobuf,"%d:",lineno));
                                         /* Print line number */
             lastmatch = cp;                /* New last match */
           }
         linelen = strncspn(cp,"\n",endbuf - cp) + 1;
                                         /* Calculate line length */
         (*write1)(cp,linelen);                /* Print the line */
         cp += linelen;                        /* Skip the line */
       }
     if(flags & LINENOS) lineno += countlines(lastmatch,endbuf);
                                         /* Count remaining lines in buffer */
     return(0);                                /* Keep searching */
   }


 void                        showv(name,lastmatch,thismatch)
 char                        *name;
 register char                *lastmatch;
 char                        *thismatch;
   {
     register int        linelen;
     int                        namlen = 0;        /* Length of name */
     char                lnobuf[LNOLEN];        /* Line number buffer */
     char                nambuf[LINELEN];/* Name buffer */

     if(flags & (SHOWNAME | LINENOS))
       {
         while(lastmatch < thismatch)
           {
             if(flags & SHOWNAME)        /* If name wanted */
               {
                 if(namlen == 0) namlen = sprintf(nambuf,"%s:",name);
                                         /* Format name if not done already */
                 (*write1)(nambuf,namlen);
                                         /* Write the name */
               }
             if(flags & LINENOS)
               {
                 (*write1)(lnobuf,sprintf(lnobuf,"%d:",lineno++));
               }
             linelen = strncspn(lastmatch,"\n",thismatch - lastmatch) + 1;
             (*write1)(lastmatch,linelen);
             lastmatch += linelen;
           }
       }
     else (*write1)(lastmatch,thismatch - lastmatch);
   }


 int                        grepvbuffer(startbuf,endbuf,name)
 char                        *startbuf;        /* Start of buffer */
 char                        *endbuf;        /* End of buffer */
 char                        *name;                /* File name */
   {
     register char        *cp;                /* Buffer pointer */
     register char        *lastmatch;        /* Pointer to line after last mat

     cp = startbuf;                        /* Initialize to start of buffer */
     lastmatch = cp;
     while((cp = (*find)(cp,endbuf)) != NULL)
       {
         if((flags & BEGLINE) && cp[-1] != '\n' && cp > startbuf)
           {                                /* If begin line conditions not me
             ++cp;                        /* Skip first char of match */
             continue;                        /* Keep looking */
           }
         status = 1;                        /* Match found */
         if(flags & NAMEONLY) return(1);        /* Skip rest of file if NAMEON
         cp -= revfind(cp,'\n',cp - startbuf);
                                         /* Point at last linefeed */
         if(*cp == '\n') ++cp;                /* Point at start of line */
         showv(name,lastmatch,cp);        /* Show from last match to this */
         cp += strncspn(cp,"\n",endbuf - cp) + 1;
                                         /* Skip over line with match */
         lastmatch = cp;                        /* New "last" match */
         ++lineno;                        /* Increment line count */
       }
     if(!(flags & NAMEONLY)) showv(name,lastmatch,endbuf);
                                         /* Show buffer tail if not NAMEONLY *
     return(0);                                /* Keep searching file */
   }


 void                        qgrep(name,fd)
 char                        *name;                /* File name */
 HFILE                        fd;                /* File descriptor */
   {
     register int        cb;                /* Byte count */
     register char        *cp;                /* Buffer pointer */
     char                *endbuf;        /* End of buffer */
     int                        taillen;        /* Length of buffer tail */
     int                        bufi;                /* Buffer index */
     char                line[LINELEN];        /* Line buffer */

     lineno = 1;                                /* File starts on line 1 */
     taillen = 0;                        /* No buffer tail yet */
     bufi = 0;                                /* Initialize buffer index */
     cp = bufptr[0];                        /* Initialize to start of buffer *
     finishread();                        /* Make sure no I/O activity */
     arrc = DosRead( fd, cp, FILBUFLEN, &cbread);
                                         /* Do first read synchronously */
     while((cb = finishread()) + taillen > 0)
       {                                        /* While search incomplete */
         if(cb == 0)                        /* If buffer tail is all that's le
           {
             taillen = 0;                /* Set tail length to zero */
             *cp++ = '\r';                /* Add end of line sequence */
             *cp++ = '\n';
             endbuf = cp;                /* Note end of buffer */
           }
         else                                /* Else start next read */
           {
             taillen = revfind(cp + cb - 1,'\n',cb);
                                         /* Find length of partial line */
             endbuf = cp + cb - taillen;        /* Get pointer to end of buffe
             cp = bufptr[bufi ^ 1];        /* Pointer to other buffer */
             memcpy(cp,endbuf,taillen);        /* Copy tail to head of other b
             cp += taillen;                /* Skip over tail */
             startread(fd,cp,(FILBUFLEN - taillen) & (~0 << LG2SECLEN));
                                         /* Start next read */
           }
         if((*grep)(bufptr[bufi],endbuf,name)) return;
                                         /* Done if NAMEONLY and match found *
         bufi ^= 1;                        /* Switch buffers */
       }
     if((flags & (NAMEONLY | INVERT)) == (NAMEONLY | INVERT))
       (*write1)(line,sprintf(line,"%s\r\n",name));
                                         /* Write name if -lv */
   }


 void                        usage(verbose)
 int                        verbose;        /* Verbose message flag */
   {
     static char                *opts[] =
       {
         "-? - print this message",
         "-B - match pattern if at beginning of line",
         "-E - match pattern if at end of line",
         "-l - print only file name if file contains match",
         "-n - print line number before each matching line",
         "-v - print only lines not containing a match",
         "-x - print lines that match exactly (-BE)",
         "-y - treat upper and lower case as equivalent",
         "-e - treat next argument as the search string",
         "-f - read search strings from file named by next argument",
         "-i - read file list from file named by next argument",
         0
       };
     register char        **opt = opts;        /* Option list */

     fprintf(stderr,"usage: CPGREP [-?BElnvxy][-e][-f <file>][-i <file>][<stri
     if(verbose)                                /* If verbose message wanted *
       {
         while(*opt != 0) fprintf(stderr,"%s\n",*opt++);
                                         /* Print option list */
       }
     DosExit( EXIT_PROCESS, 2);                /* Error exit */
   }


 void                        main(argc,argv)
 int                        argc;
 char                        **argv;
   {
     register char        *cp;
     HFILE                fd;
     FILE                *fi;
     char                filnam[FILNAMLEN];
     USHORT                handType;
     USHORT                handAttrib;
     int                        i;
     char                *inpfile = NULL;
     int                 j;
     char                *seplist = " \t";
     int                        strcnt;
     char                *strfile = NULL;
     long                start;                /* Start time */
     int                        (*add)();
     BYTE                t2stk[2*STKLEN];  /* Read thread stack */
     BYTE                t3stk[2*STKLEN];  /* Write thread stack */
     long                time();                /* Time and date in seconds */

     DosGetMachineMode((char far *) &pmode);
     flags = 0;
     for(i = 1; i < argc && argv[i][0] == '-'; ++i)
       {
         switch(argv[i][1])
           {
             case 'f':
             case 'i':
               if(i == argc - 1)
                 {
                   fprintf(stderr,"File name missing after -%c\n",argv[i][1]);
                   DosExit( EXIT_PROCESS, 2);
                 }
               if(argv[i++][1] == 'i') inpfile = argv[i];
               else strfile = argv[i];
               break;

             case '?':
             case 'B':
             case 'E':
             case 'N':
             case 'S':
             case 'd':
             case 'l':
             case 'n':
             case 't':
             case 'v':
             case 'x':
             case 'y':
               for(cp = &argv[i][1]; *cp != '\0'; ++cp)
                 {
                   switch(*cp)
                     {
                       case '?':
                         usage(1);        /* Verbose usage message */

                       case 'B':
                         flags |= BEGLINE;
                         break;

                       case 'E':
                         flags |= ENDLINE;
                         break;

                       case 'N':
                         grep = grepnull;
                         break;

                       case 'S':
                         pmode = 0;        /* Force synchronous I/O */
                         break;

                       case 'd':
                         flags |= DEBUG;
                         break;

                       case 'l':
                         flags |= NAMEONLY;
                         break;

                       case 'n':
                         flags |= LINENOS;
                         break;

                       case 't':
                         flags |= TIMER;
                         break;

                       case 'v':
                         status = 0;        /* Assume success */
                         flags |= INVERT;
                         grep = grepvbuffer;
                         break;

                       case 'x':
                         flags |= BEGLINE | ENDLINE;
                         break;

                       case 'y':
                         casesen = 0;
                         break;

                       default:
                         fprintf(stderr,"-%c ignored\n",*cp);
                         break;
                     }
                 }
               break;

             case 'e':
               if(strfile == NULL)
                 {
                   ++i;
                   seplist = "";                /* Allow anything in string */
                   goto endfor0;
                 }
               /* Drop through to "default" */

             default:
               fprintf(stderr,"%s ignored\n",argv[i]);
               break;
           }
       }
     endfor0:

     if(i == argc && strfile == NULL) usage(0);
                                         /* Simple usage message if arg error
     if(flags & TIMER) start = time(NULL);
                                         /* Get start time if timer on */
     if(pmode)                                /* Initialize semaphores and thr
       {
         TID threadId;

         DosSemClear( &readdone);
         DosSemClear( &writedone);
         DosSemSet( &readpending);
         DosSemSet( &writepending);
         if(DosCreateThread(thread2, &threadId, t2stk + 2*STKLEN) != 0 ||
            DosCreateThread(thread3, &threadId, t3stk + 2*STKLEN) != 0)
           {                                /* If thread creation fails */
             fprintf(stderr,"Failed to create child threads\n");
                                         /* Print error message */
             DosExit( EXIT_PROCESS, 2);        /* Die */
           }
       }
     setmode(fileno(stdout),O_BINARY);
     add = addplain;                        /* Assume plain string adds */
     if(strfile != NULL)                        /* If strings from file */
       {
         if(!(flags & BEGLINE)) add = addfancy;
                                         /* Try to add intelligently */
         if((fd = open(strfile,0)) == -1)
           {                                /* If open fails */
             fprintf(stderr,"Cannot read strings from %s\n",strfile);
             DosExit( EXIT_PROCESS, 2);                          /* Print mess
           }
         for(cp = filbuf, j = 0; (j = read(fd,cp,FILBUFLEN*2 - j)) > 0; cp +=
                                         /* Read strings file into buffer */
         j = cp - filbuf;                /* Get total length of buffer */
         close(fd);                        /* Close strings file */
         filbuf[j] = '\0';                /* Null-terminate the buffer */
         cp = filbuf;                        /* Set pointer to string list */
         seplist = "\r\n";                /* Only '\r' and '\n' are separators
       }
     else                                /* Else strings on command line */
       {
         cp = argv[i++];                        /* Set pointer to strings */
         j = strlen(cp);                        /* Get length of strings */
       }
     if((strcnt = (*add)(cp,j,seplist)) == 0)
       {                                        /* If no strings */
         fprintf(stderr,"No search strings\n");
         DosExit( EXIT_PROCESS, 2);        /* Print error message and die */
       }

     /*
      *  Check type of handle for std. out.
      */
     if(DosQHandType(fileno(stdout), &handType, &handAttrib) != 0)
       {                                        /* If error */
         fprintf(stderr,"Standard output bad handle\n");
                                         /* Print error message */
         DosExit( EXIT_PROCESS, 2);        /* Die */
       }
     if(handType != 0 && (handAttrib & ISCOT))
       {                                 /* If handle is console output */
         write1 = write1nobuf;                /* Use unbuffered output */
         flush1 = flush1nobuf;
       }

     if(strcnt > 1)                        /* If more than one string */
       {
         if(flags & DEBUG)                /* Print debug info maybe */
           {
             fprintf(stderr,"Here are the strings:\n");
             dumpstrings();
           }
       }
     else if(casesen) find = findone;        /* Else use findone() */
     if(inpfile != NULL)                        /* If file list from file */
       {
         flags |= SHOWNAME;                /* Always show name of file */
         if((fi = fopen(inpfile,"r")) == NULL)
           {                                /* If open fails */
             fprintf(stderr,"Cannot read file list from %s\r\n",inpfile);
                                         /* Error message */
             DosExit( EXIT_PROCESS, 2);        /* Error exit */
           }
         while(fgets(filnam,FILNAMLEN,fi) != NULL)
           {                                /* While there are names */
             filnam[strcspn(filnam,"\r\n")] = '\0';
                                         /* Null-terminate the name */
             if((fd = openfile(filnam)) == -1) continue;
                                         /* Skip file if it cannot be opened *
             qgrep(filnam,fd);                /* Do the work */
             close(fd);                        /* Close the file */
           }
         fclose(fi);                        /* Close the list file */
       }
     else if(i == argc)
       {
         flags &= ~(NAMEONLY | SHOWNAME);
         setmode(fileno(stdin),O_BINARY);
         qgrep(NULL,fileno(stdin));
       }
     if(argc > i + 1) flags |= SHOWNAME;
     for(; i < argc; ++i)
       {
         if((fd = openfile(argv[i])) == -1) continue;
         qgrep(argv[i],fd);
         close(fd);
       }
     (*flush1)();
     if(flags & TIMER) fprintf(stderr,"%ld seconds\n",time(NULL) - start);
                                         /* Print elapsed time if timer on */
     DosExit( EXIT_PROCESS, status);
   }


 CPGREPSB.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CPGREP\CPGREPSB.ASM

 ; Created by Microsoft Corp. 1986
 name cpgrepsub

 retlen        equ        2                        ; Size of return address on

 dgroup        group        _data

 extrn        _casesen:        word                ; Case-sensitivity flag
 extrn        _stringlist:        word                ; Table of string lists
 extrn        _target:        byte                ; Target string
 extrn        _targetlen:        word                ; Length of target string
 extrn        _transtab:        byte                ; Translation table for _f

 ;        This segment is puposely word-aligned.  See note
 ;        in _findlist below.

 _text        segment word public 'code'
         assume        cs:_text, ds:dgroup, es:nothing, ss:dgroup

 ; char                        *findone(buffer,bufend)
 ; char                        *buffer                /* Buffer in which to se
 ; char                        *bufend;        /* End of buffer */
 ;
 ; NOTE: targetlen MUST BE greater than zero

 buffer        equ        word ptr [bp+retlen+2]
 bufend        equ        word ptr [bp+retlen+4]

         EVEN

         public        _findone
 _findone proc        near
         push        bp
         mov        bp,sp
         push        di
         push        si
         push        es
         push        ds                        ; ES = DS
         pop        es
         mov        cx,bufend                ; CX = end of buffer
         mov        di,buffer                ; ES:DI = buffer
         sub        cx,di                        ; CX = length of buffer
         jbe        sfnomatch                ;  length less than or equal to z
         mov        dx,_targetlen                ; DX = length of target
         dec        dx                        ; Decrement it
         sub        cx,dx                        ; target must fit in buffer
         jbe        sfnomatch                ;  (no match if buffer too short)

 ;        CX = buffer length
 ;        DX = target length (minus first character)
 ;        ES:DI = buffer pointer

 sf0:        jcxz        sfnomatch                ; No match if count zero
         mov        si,offset dgroup:_target ; DS:SI = target
         lodsb                                ; AL = first byte of target
         repne scasb                        ; Look for first character
         jne        sfnomatch                ;  jump if not found
         mov        bx,cx                        ; BX = buffer length
         mov        ax,di                        ; AX = buffer pointer
         mov        cx,dx                        ; Get count for cmpsb
         or        cx,cx                        ; Zero? (JCXZ doesn't set flag
         je        sf1                        ;  yes, skip compare
         repe cmpsb                        ; Do string compare
 sf1:        mov        di,ax                        ; DI = buffer pointer
         mov        cx,bx                        ; CX = buffer length
         jne        sf0                        ; Loop if no match
         dec        ax                        ; AX = offset of start of match
         jmp        short sf4

 sfnomatch:
         xor        ax,ax                        ; No match
 sf4:        pop        es
         pop        si
         pop        di
         pop        bp
         ret
 _findone endp


 ; int                        revfind(s,c,slen)
 ; char                        *s;                /* String to search */
 ; int                        c;                /* Char to search for */
 ; int                        slen;                /* Length of s */

 s        equ        [bp+retlen+2]
 c        equ        [bp+retlen+4]
 slen        equ        [bp+retlen+6]

         EVEN

         public        _revfind
 _revfind proc        near
         push        bp
         mov        bp,sp
         push        di
         push        es
         push        ds
         pop        es
         mov        di,s
         mov        ax,c
         mov        cx,slen
         jcxz        rf1
         std
         repne scasb
         cld
         mov        cx,s
         jne        rf0
         inc        di
 rf0:        sub        cx,di
 rf1:        mov        ax,cx
         pop        es
         pop        di
         pop        bp
         ret
 _revfind endp


 ; int                        countlines(start,finish)
 ; char                        *start;
 ; char                        *finish;

 start        equ        [bp+retlen+2]
 finish        equ        [bp+retlen+4]

         EVEN

         public        _countlines
 _countlines proc near
         push        bp
         mov        bp,sp
         push        di
         push        es
         push        ds
         pop        es
         xor        dx,dx                        ; Accumulate count in DX
         mov        di,start                ; ES:DI points to start
         mov        cx,finish                ; Put length in CX
         sub        cx,di
         jbe        cl1                        ;  branch if no bytes
         mov        al,0Ah                        ; Search for linefeeds
 cl0:        jcxz        cl1                        ; Exit loop if count zero
         repne scasb                        ; Do search
         jne        cl1                        ;  branch if none found
         inc        dx                        ; Increment count
         jmp        short cl0                ; Loop
 cl1:        mov        ax,dx                        ; Return line count in AX
         pop        es
         pop        di
         pop        bp
         ret
 _countlines endp


 ; char                        *findlist(buffer,bufend)
 ; char                        *buffer;        /* Buffer to search */
 ; char                        *bufend;        /* End of buffer */

 savesi        equ        word ptr [bp-2]
 endbyte        equ        byte ptr [bp-4]

 stringnode struc
         s_alt        dw        ?                ; List of alternate portions
         s_suf        dw        ?                ; Pointer to suffix string li
         s_must        dw        ?                ; Length of portion that mus
 stringnode ends

         EVEN

 flworker dw        findsubi, findsub        ; Worker dispatch table

         public        _findlist
 _findlist proc        near
         ASSUME        DS:DGROUP, ES:NOTHING, SS:DGROUP

         push        bp
         mov        bp,sp
         sub        sp,4                        ; Make room for local vars
         push        di
         push        si
         push        ds
         pop        es
     ASSUME        ES:DGROUP

 ;        We mark the end of our search buffer with 0FFh so that
 ;        any comparisons that might run past the end of the buffer
 ;        will fail on the 0FFh.  We choose 0FFh so that if the
 ;        comparison fails on it, it will always appear as though
 ;        the string in the buffer is greater that the string in
 ;        the search list.  This will prevent us from stopping
 ;        the search too soon.  Of course, we must restore the byte
 ;        when we're done.

         mov        bx,bufend                ; BX = end of buffer
         mov        al,0FFh                        ; End marker
         xchg        byte ptr [bx],al        ; AL = byte after end of buffer
         mov        endbyte,al                ; Save the byte

         mov        cx,bx                        ; CX = end of buffer
         mov        si,buffer                ; SI = buffer
         sub        cx,si                        ; CX = buffer length
         jbe        fl1                        ;  no match if empty buffer
         mov        bx,offset dgroup:_transtab ; BX = translation table addres

         mov        di,_casesen                ; Get flag
         shl        di,1                        ; Scale to word index
         call        cs:flworker[di]                ; Call helper
         jc        fl1                        ;  branch if no match

 ;        We have a match
 ;
 ;        SI = offset of first character past end of matched string
 ;        savesi = offset of first character past start of matched string

         mov        ax,savesi                ; AX = 1st char past start
         dec        ax                        ; AX = start of matched string
         jmp        short fl2

 ;        We did not find a match

 fl1:
         xor        ax,ax                        ; Return NULL

 ;        Restore end byte before leaving

 fl2:
         mov        bx,bufend                ; BX = end of buffer
         mov        dl,endbyte                ; DL = end byte
         mov        [bx],dl                        ; Restore byte

         pop        si
         pop        di
         mov        sp,bp
         pop        bp
         ret

 _findlist endp


 ;***        findsub - case-sensitive worker for _findlist
 ;
 ;        This function does most of the work for
 ;        case-sensitive multi-string searches.
 ;
 ;        ENTRY        BX = address of translation table
 ;                CX = number of bytes left in buffer
 ;                DS:SI = buffer pointer
 ;                SS:BP = pointer to stack frame for _findlist
 ;        EXIT        Carry set
 ;                    No match
 ;                Carry clear
 ;                    DS:SI = pointer to first character after match
 ;        USES        AX, CX, DX, DI, SI, Flags

         EVEN

         public        findsub, fs0, fs1, fs2, fs3, fs4, fs5, fs6
 findsub        proc        near
         ASSUME        DS:DGROUP, ES:DGROUP, SS:DGROUP

 fs0:
         xor        ax,ax                        ; AH = 0

 ;        AH = 0
 ;        BX = address of translation table
 ;        CX = number of bytes left in buffer
 ;        SI = buffer pointer
 ;        DS = ES = SS = DGROUP

 fs1:
         lodsb                                ; Character in AL
         xlat byte ptr [bx]                ; Translate character to index
         or        al,al                        ; Zero means invalid 1st byte
         loopz        fs1                        ;  if so, try next character

 ;        Either the zero bit is set, meaning the buffer is empty,
 ;        or the zero bit is clear, meaning we have a valid first
 ;        character.  Either way, CX has been decremented.

         jz        fs6                        ;  branch if buffer empty
         mov        savesi,si                ; Save buffer pointer
         shl        ax,1                        ; Scale to word index
         mov        di,ax
         mov        di,_stringlist[di]        ; DI points to string record
         or        di,di                        ; One byte match? (OR clears c
         jz        fs3                        ;  yes, skip ahead

 ;        Loop to search for match.
 ;        BX = address of translation table
 ;        DI = pointer to string record
 ;        SI = pointer into buffer

 fs2:
         mov        cx,[di].s_must                ; CX = length of string
         sub        di,cx                        ; DI = pointer to string
         mov        dx,si                        ; Save pointer to start of su
         repe cmpsb                        ; Strings match?
         ja        fs4                        ;  no, try alternate if follows
         jb        fs5                        ;  no, cannot be in this list
         add        di,cx                        ; DI = pointer to string reco
         mov        di,[di].s_suf                ; Get pointer to suffix strin
         or        di,di                        ; Is there one? (OR clears car
         jnz        fs2                        ;  yes, keep looking

 ;        Match found

 fs3:
         ret                                ;  no, we have a match

 ;        Try alternate suffix

 fs4:
         add        di,cx                        ; DI = pointer to string reco
         mov        di,[di].s_alt                ; Get pointer to alternate
         mov        si,dx                        ; Restore SI to start of suff
         or        di,di                        ; Is there one?
         jnz        fs2                        ;  yes, loop

 ;        Try new first character

 fs5:
         mov        cx,bufend                ; CX = end of buffer
         mov        si,savesi                ; Restore SI to saved value
         sub        cx,si                        ; CX = length of buffer
         ja        short fs0                ; Try next character in buffer

 ;        No match

 fs6:
         stc                                ; No match
         ret

 findsub        endp


 ;***        findsubi - case-insensitive worker for _findlist
 ;
 ;        This function does most of the work for
 ;        case-insensitive multi-string searches.
 ;
 ;        ENTRY        BX = address of translation table
 ;                CX = number of bytes left in buffer
 ;                DS:SI = buffer pointer
 ;                SS:BP = pointer to stack frame for _findlist
 ;        EXIT        Carry set
 ;                    No match
 ;                Carry clear
 ;                    DS:SI = pointer to first character after match
 ;        USES        AX, CX, DX, DI, SI, Flags

         EVEN

         public        findsubi
 findsubi proc        near
         ASSUME        DS:DGROUP, ES:DGROUP, SS:DGROUP

 fsi0:
         xor        ax,ax                        ; AH = 0

 ;        AH = 0
 ;        BX = address of translation table
 ;        CX = number of bytes left in buffer
 ;        SI = buffer pointer
 ;        DS = ES = SS = DGROUP

 fsi1:
         lodsb                                ; Character in AL
         xlat byte ptr [bx]                ; Translate character to index
         or        al,al                        ; Zero means invalid 1st byte
         loopz        fsi1                        ;  if so, try next character

 ;        Either the zero bit is set, meaning the buffer is empty,
 ;        or the zero bit is clear, meaning we have a valid first
 ;        character.  Either way, CX has been decremented.

         jz        fsi7                        ;  branch if buffer empty
         mov        savesi,si                ; Save buffer pointer
         shl        ax,1                        ; Scale to word index
         mov        di,ax
         mov        di,_stringlist[di]        ; DI points to string record
         or        di,di                        ; One byte match? (OR clears c
         jz        fsi4                        ;  yes, skip ahead

 ;        Loop to search for match.
 ;        BX = address of translation table
 ;        DI = pointer to string record
 ;        SI = pointer into buffer

 fsi2:
         mov        cx,[di].s_must                ; CX = length of string
         sub        di,cx                        ; DI = pointer to string
         mov        dx,si                        ; Save pointer to start of su
 fsi3:        lodsb                                ; Byte in AL, SI = SI + 1
         mov        ah,[di]                        ; Byte in AH, DI = DI + 1
         inc        di
         or        ax,2020h                ; Fold bytes onto lower case
         cmp        al,ah                        ; Compare bytes
         loope        fsi3                        ; Loop while same
         ja        fsi5                        ;  no, try alternate if follows
         jb        fsi6                        ;  no, cannot be in this list
         add        di,cx                        ; DI = pointer to string reco
         mov        di,[di].s_suf                ; Get pointer to suffix strin
         or        di,di                        ; Is there one? (OR clears car
         jnz        fsi2                        ;  yes, keep looking

 ;        Match found

 fsi4:
         ret                                ;  no, we have a match

 ;        Try alternate suffix

 fsi5:
         add        di,cx                        ; DI = pointer to string reco
         mov        di,[di].s_alt                ; Get pointer to alternate
         mov        si,dx                        ; Restore SI to start of suff
         or        di,di                        ; Is there one?
         jnz        fsi2                        ;  yes, loop

 ;        Try new first character

 fsi6:
         mov        cx,bufend                ; CX = end of buffer
         mov        si,savesi                ; Restore SI to saved value
         sub        cx,si                        ; CX = length of buffer
         ja        short fsi0                ; Try next character in buffer

 ;        No match

 fsi7:
         stc                                ; No match
         ret

 findsubi endp


 ; int                        strnspn(s,t,n)
 ; char                        *s;                /* String to search */
 ; char                        *t;                /* Target list */
 ; int                        n;                /* Length of s */

 s        equ        word ptr [bp+retlen+2]
 t        equ        word ptr [bp+retlen+4]
 n        equ        word ptr [bp+retlen+6]

         EVEN

         public        _strnspn
 _strnspn proc        near
         push        bp
         mov        bp,sp
         push        di
         push        si
         push        ds
         pop        es
         cld
         mov        bx,t                        ; BX = t
         mov        di,bx                        ; DI = t
         xor        al,al                        ; Search for 0 byte
         mov        cx,0FFFFh
         repne scasb
         dec        di                        ; Back up to 0
         sub        di,bx                        ; DI = length of t
         jz        spn1                        ; Done if length of t is 0
         mov        dx,di                        ; DX = length of t
         mov        si,s                        ; SI = s
         mov        cx,n                        ; CX = length of s
         jcxz        spn1                        ; Check for null string
         push        bp
 spn0:        lodsb                                ; AL = next char in s
         mov        bp,cx                        ; BP = length of s
         mov        cx,dx                        ; CX = length of t
         mov        di,bx                        ; DI = t
         repne scasb                        ; Scan until match found
         mov        cx,bp                        ; CX = length of s
         loope        spn0                        ; Loop if match found
         pop        bp
         je        spn1                        ; Skip ahead if end of s reache
         dec        si                        ; Back up one char
 spn1:        sub        si,s                        ; SI = length of prefix
         mov        ax,si                        ; AX = length of prefix
         pop        si
         pop        di
         pop        bp
         ret
 _strnspn endp


 ; int                        strncspn(s,t,n)
 ; char                        *s;                /* String to search */
 ; char                        *t;                /* Target list */
 ; int                        n;                /* Length of s */

         EVEN

         public        _strncspn
 _strncspn proc        near
         push        bp
         mov        bp,sp
         push        di
         push        si
         push        ds
         pop        es
         cld
         mov        bx,t                        ; BX = t
         mov        di,bx                        ; DI = t
         xor        al,al                        ; Search for 0 byte
         mov        cx,0FFFFh
         repne scasb
         dec        di                        ; Back up to 0
         sub        di,bx                        ; DI = length of t
         mov        ax,n                        ; Assume length of t is 0
         jz        cspn2                        ; Done if length of t is 0
         mov        dx,di                        ; DX = length of t
         mov        si,s                        ; SI = s
         mov        cx,ax                        ; CX = length of s
         jcxz        cspn1                        ; Check for null string
         push        bp
 cspn0:        lodsb                                ; AL = next char in s
         mov        bp,cx                        ; BP = length of s
         mov        cx,dx                        ; CX = length of t
         mov        di,bx                        ; DI = t
         repne scasb                        ; Scan until match found
         mov        cx,bp                        ; CX = length of s
         loopne        cspn0                        ; Loop if match not found
         pop        bp
         jne        cspn1                        ; Skip ahead if end of s reac
         dec        si                        ; Back up one char
 cspn1:        sub        si,s                        ; SI = length of prefix
         mov        ax,si                        ; AX = length of prefix
 cspn2:        pop        si
         pop        di
         pop        bp
         ret
 _strncspn endp


 ;        cmpsen - case-sensitive comparison
 ;
 ;        ENTRY        DS:SI = buffer
 ;                ES:DI = string
 ;                CX = length of string
 ;        EXIT        CX = length of string unused
 ;                DI = unused portion of string
 ;                Z set
 ;                    match found
 ;                Z clear
 ;                    no match
 ;        USES        CX, DI, SI, Flags

         EVEN

 cmpsen        proc        near
         repe cmpsb
         ret
 cmpsen        endp


 ;        cmpinsen - case-insensitive comparison
 ;
 ;        ENTRY        DS:SI = buffer
 ;                ES:DI = string
 ;                CX = length of string
 ;        EXIT        CX = length of string unused
 ;                DI = unused portion of string
 ;                Z set
 ;                    match found
 ;                Z clear
 ;                    no match
 ;        USES        AX, CX, DI, SI, Flags

         EVEN

 cmpinsen proc        near
 cmpi0:        lodsb                                ; Byte in AL, SI = SI + 1
         mov        ah,[di]                        ; Byte in AH, DI = DI + 1
         inc        di
         or        ax,2020h                ; Fold bytes onto lower case
         cmp        al,ah                        ; Compare bytes
         loope        cmpi0                        ; Loop while same
         ret
 cmpinsen endp


 ; void                        matchstrings(s1,s2,len,nmatched,leg)
 ; char                        *s1;                /* First string */
 ; char                        *s2;                /* Second string */
 ; int                        len;                /* Length */
 ; int                        *nmatched;        /* Number of bytes matched */
 ; int                        *leg;                /* Less than, equal, greate

 cm_s1                equ        word ptr [bp+retlen+2]
 cm_s2                equ        word ptr [bp+retlen+4]
 cm_len                equ        word ptr [bp+retlen+6]
 cm_nmatched        equ        word ptr [bp+retlen+8]
 cm_leg                equ        word ptr [bp+retlen+10]

         EVEN

         public        _matchstrings
 _matchstrings proc near
         ASSUME        DS:DGROUP, ES:NOTHING, SS:DGROUP

         push        bp
         mov        bp,sp
         push        di
         push        si
         push        ds
         pop        es
     ASSUME        ES:DGROUP
         mov        di,cm_s2
         mov        si,cm_s1
         mov        cx,cm_len
         cmp        _casesen,0
         je        cm0
         call        cmpsen
         jmp        short cm1
 cm0:        call        cmpinsen
 cm1:        mov        bx,cm_leg
         mov        word ptr [bx],0                ; Assume equal
         jz        cm2                        ;  yes, skip ahead
         mov        word ptr [bx],1                ; Assume greater than
         jg        cm1a                        ;  yes, skip ahead
         mov        word ptr [bx],-1        ; Less than
 cm1a:        dec        si
 cm2:        sub        si,cm_s1
         mov        bx,cm_nmatched
         mov        [bx],si
         pop        si
         pop        di
         pop        bp
         ret

 _matchstrings endp


 ; int                        strcmp(s1,s2)
 ; char                        *s1;                /* First string */
 ; char                        *s2;                /* Second string */

         public        _strcmp
 _strcmp        proc        near
         push        bp
         mov        bp,sp
         push        di
         push        si
         push        ds
         pop        es
         mov        si,[bp+4]                ; DS:SI = s1
         mov        di,[bp+6]                ; ES:DI = s2
 sc0:        lodsb                                ; AL = *s1++
         scasb                                ; AL - *s2++
         jne        sc1                        ;  branch if no match
         or        al,al                        ; End of s1?
         jne        sc0                        ;  no, loop
         cbw                                ; AX = 0
         jmp        short sc2                ; Exit
 sc1:        mov        ax,1                        ; Assume s1 > s2
         jg        sc2                        ;  yes, branch
         neg        ax                        ; s1 < s2
 sc2:        pop        si
         pop        di
         pop        bp
         ret
 _strcmp        endp


         public        _bpt
 _bpt        proc        near
         int        3
         ret
 _bpt        endp

 _text        ends

 _data        segment word public 'data'
 _data        ends

 end


 DATA.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\DATA.C

 /***************************************************************************\
 * DATA.C -- This file contains per process global variables
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define NO_DOS
 #define NO_GPI
 #include "tool.h"

 /*
    This library uses a NON SHARED DATA selector.  This means each
    process using the library gets its own selector, and also that
    values cannot be shared and must be recreated for each process.
 */

 HMODULE vhModule;            /* Library module handle */
 HHEAP  vhheap;               /* Library heap */

 PSTR   vrgsz[CSTRINGS];      /* Array of pointer to our strings (indexed
                                 by IDS_... */


 DCALC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CALC\DCALC\DCALC.C

 /****************************** Module Header ******************************\
 * Module Name:        dcalc.c - Dialog form of the Calc application
 *
 * OS/2 Presentation Manager version of Calc, ported from Windows version
 *
 * Created by Microsoft Corporation, 1989
 *
 \***************************************************************************/

 #define INCL_DEV
 #define INCL_DOSPROCESS
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSNLS
 #define INCL_ERRORS
 #define INCL_WINBUTTONS
 #define INCL_WINCLIPBOARD
 #define INCL_WINDIALOGS
 #define INCL_WINFRAMEMGR
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WINSWITCHLIST
 #define INCL_WINTRACKRECT
 #define INCL_WINWINDOWMGR
 #include <os2.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include "dcalc.h"

 /************* GLOBAL VARIABLES         */

 char chLastKey, currkey;
 char szCalcClass[] = "Calculator";
 char szTitle[30];
 char szreg1[20], szreg2[20], szmem[20], szregx[20];
 /* hope 20 is enough for kanji error string */
 char szErrorString[20], szPlusMinus[2];
 short charwidth, charheight;
 int aspectx, aspecty, nchszstr;
 extern BOOL fError  = FALSE;
 BOOL fValueInMemory = FALSE;
 BOOL fMDown = FALSE;
 UCHAR mScan = 0;

 #define TOLOWER(x)   ( (((x) >= 'A') && ((x) <= 'Z')) ? (x)|0x20 : (x))
 #define WIDTHCONST  28
 #define CXCHARS     37
 #define CYCHARS     13

 HAB hab;
 HDC hdcLocal;                            /* Local used for button bitmap */
 HPS hpsLocal;
 HDC hdcSqr;                            /* Sqr used for square-root bitmap */
 HPS hpsSqr;
 HBITMAP hbmLocal, hbmSqr;
 HMQ  hmqCalc            = NULL;

 HWND hwndCalc            = NULL,
      hwndMenu            = NULL;

 HPOINTER hptrFinger = NULL,
          hptrIcon   = NULL;

 DEVOPENSTRUC dop =                    /* used by DevOpenDC */
 {
     NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL
 };

 static char bButtonValues[] =            /* Button values */
 {
     0xBC, 0xBB, 0xBA, 0xB9,  '0',  '1',  '2',  '3',  '4',
      '5',  '6',  '7',  '8',  '9',  '.',  '/',  '*',  '-',
      '+',  'q',  '%',  'c',  '=', 0xB1, NULL
 };

 /************* PROCEDURE DECLARATIONS   */

 MPARAM EXPENTRY AboutDlgProc(HWND, USHORT, MPARAM, MPARAM);
 BOOL CalcInit(VOID);
 VOID CalcPaint( HWND, HPS);
 MRESULT EXPENTRY fnDlgCalc(HWND, USHORT, MPARAM, MPARAM);
 VOID cdecl main(VOID);
 VOID DataXCopy( VOID);
 VOID DataXPaste( VOID);
 VOID DrawNumbers( HPS);
 VOID Evaluate(BYTE);
 VOID InitCalc( VOID);
 BOOL InterpretChar( CHAR);
 VOID ProcessKey(HWND, WPOINT *);
 char Translate(WPOINT *);
 VOID UpdateDisplay( VOID);



 /********************************************************************
    Write the appropriate number or error string to the display area
    and mark memory-in-use if appropriate.
  */

 BYTE aszDisplayBuff[20];

 VOID UpdateDisplay()
 {
     strcpy(aszDisplayBuff, fError? "Error" :szreg1);
     strcat(aszDisplayBuff, fValueInMemory? " M" : "  ");

     WinSetDlgItemText(hwndCalc, TXT_RESULT_DISPLAY, aszDisplayBuff);
 }


 /**********************************************************************
     Display helpful info
  */

 MPARAM EXPENTRY AboutDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     if (msg == WM_COMMAND)
     {
         WinDismissDlg(hwnd, TRUE);
         return(MPFROMSHORT(TRUE));
     }
     else return(WinDefDlgProc(hwnd, msg, mp1, mp2));
 }


 /**********************************************************************
     General initialization
  */

 BOOL CalcInit()
 {
     hab = WinInitialize(0);

     hmqCalc = WinCreateMsgQueue( hab, 0);

     return(TRUE);
 }

 /**********************************************************************
     main procedure
  */

 VOID cdecl main()
 {
     QMSG qmsg;

     if (!CalcInit()) {                            /* general initialization *
         WinAlarm(HWND_DESKTOP, 0xffff);
         goto exit;
     }

     WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP, fnDlgCalc, NULL, CALCDLG, NULL);

     if (hwndCalc)
         while (WinGetMsg( hab, (PQMSG)&qmsg, NULL, 0, 0))
             WinDispatchMsg( hab, (PQMSG)&qmsg);

 exit:                                            /* clean up */

     if (hwndMenu)      WinDestroyWindow(hwndMenu);

     WinDestroyMsgQueue(hmqCalc);
     WinTerminate(hab);

     DosExit(EXIT_PROCESS, 0);                    /* exit without error */
 }


 /*************************************************************************
    Calc Dialog Window Procedure
  */


 USHORT        idProcess, idThread;
 SWCNTRL swc;
 HSWITCH hsw;
 USHORT        usWidthCalc, usHeightCalc;

 MRESULT EXPENTRY fnDlgCalc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     RECTL rectl;
     BOOL fClip;
     USHORT fi, idCtrl;
     MRESULT mresult;
     USHORT  afSWP;
     PSWP    pswp;

     static BOOL fMinimized;


     switch (msg)
     {
     case WM_INITDLG:

 /* Set up the global state assumed by the dialog.
  */
         hwndCalc = hwnd;
         hwndMenu = WinLoadMenu(hwnd, NULL, IDR_CALC);

         fMinimized = FALSE;

         hptrFinger = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDP_FINGER);
         hptrIcon   = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDR_CALC);

         WinSetWindowULong(hwndCalc, QWL_STYLE,
                           FS_ICON | WinQueryWindowULong(hwndCalc, QWL_STYLE)
                          );

         WinSendMsg(hwndCalc, WM_SETICON,     (MPARAM) hptrIcon, 0L);
         WinSendMsg(hwndCalc, WM_UPDATEFRAME, (MPARAM) 0L,        0L);

         WinQueryWindowRect(hwndCalc, &rectl);
          usWidthCalc= (SHORT) (rectl.xRight - rectl.xLeft);
         usHeightCalc= (SHORT) (rectl.yTop   - rectl.yBottom);

         WinQueryWindowProcess(hwndCalc, &idProcess, &idThread);

         WinLoadString(NULL, NULL, 1, 30, (PSZ)szTitle);
         WinLoadString(NULL, NULL, 2, 20, (PSZ)szErrorString);
         WinLoadString(NULL, NULL, 3, 2,  (PSZ)szPlusMinus);

         strcpy(swc.szSwtitle, szTitle);
         swc.hwnd          = hwndCalc;
         swc.hwndIcon          = hptrIcon;
         swc.hprog          = (ULONG)NULL;
         swc.idProcess          = idProcess;
         swc.idSession          = (USHORT)0;
         swc.uchVisibility = SWL_VISIBLE;
         swc.fbJump          = SWL_JUMPABLE;
         hsw                  = WinAddSwitchEntry((PSWCNTRL)&swc);

         InitCalc();                            /* arithmetic initialization *

         WinSetActiveWindow(HWND_DESKTOP, hwndCalc);

         WinSetFocus(HWND_DESKTOP, hwndCalc);

         break;

     case WM_MINMAXFRAME:

         pswp= PVOIDFROMMP(mp1);

         if (pswp->fs & SWP_MINIMIZE) fMinimized= TRUE;
         else
             if (pswp->fs & SWP_RESTORE) fMinimized= FALSE;

         return(WinDefDlgProc(hwnd, msg, mp1, mp2));

         break;

     case WM_DESTROY:

         WinDestroyPointer(hptrIcon  );        hptrIcon  = NULL;
         WinDestroyPointer(hptrFinger);        hptrFinger= NULL;

         break;

     case WM_INITMENU:

         fClip = FALSE;

         if (WinOpenClipbrd(NULL))
         {
             fClip = WinQueryClipbrdFmtInfo(NULL, CF_TEXT, (USHORT FAR *)&fi);
             WinCloseClipbrd(NULL);
         }

         WinSendMsg((HWND)mp2, MM_SETITEMATTR,
                    (MPARAM) MAKELONG(CMD_PASTE, TRUE),
                    (MPARAM) MAKELONG(MIA_DISABLED, fClip ? 0 : MIA_DISABLED))
         break;

     case WM_ADJUSTWINDOWPOS:

         mresult= WinDefDlgProc(hwnd, msg, mp1, mp2);

         if (fMinimized) return(mresult);

         afSWP= (pswp= (PSWP) mp1)->fs;

         if (         afSWP & (SWP_SIZE     | SWP_MAXIMIZE)
             && !(afSWP &  SWP_MINIMIZE)
            )
         {
             pswp->y += pswp->cy - usHeightCalc;
             pswp->cx =        usWidthCalc;
             pswp->cy = usHeightCalc;
         }

         return(mresult);


     case WM_COMMAND:

         fError = FALSE;

         idCtrl= SHORT1FROMMP(mp1);

         if (   SHORT1FROMMP(mp2) == BN_CLICKED
             && idCtrl >= BUTTON_MC
             && idCtrl <= BUTTON_CHANGE_SIGN
            )
         {
             Evaluate(bButtonValues[idCtrl-BUTTON_MC]);
             UpdateDisplay();
         }
         else
             switch(idCtrl)
             {
             case CMD_COPY:
                 DataXCopy();                        /* copy to clipboard */
                 break;
             case CMD_PASTE:
                 DataXPaste();                        /* paste from clipboard
                 break;
             case CMD_EXIT:
                 WinPostMsg(hwndCalc, WM_QUIT, 0L, 0L);
                 break;
             case CMD_ABOUT:
                 WinDlgBox(HWND_DESKTOP, hwndCalc, (PFNWP)AboutDlgProc, NULL,
                           1, (PSZ)NULL);
                 break;
             }
         break;

     case WM_CLOSE:
         WinPostMsg(hwndCalc, WM_QUIT, 0L, 0L);
         break;

     case WM_CONTROLPOINTER:
         if (!fMinimized) return(hptrFinger);
         else return(WinDefDlgProc(hwnd, msg, mp1, mp2));

     case WM_MOUSEMOVE:
         if (!fMinimized) WinSetPointer(HWND_DESKTOP, hptrFinger);
         break;

     case WM_BUTTON1DOWN:

         return(WinDefDlgProc(hwnd, WM_TRACKFRAME, (MPARAM) TF_MOVE, mp2));

         break;

     case WM_CHAR:

         fError = FALSE;
         if (SHORT1FROMMP(mp1) & KC_KEYUP)
         {
             if (CHAR4FROMMP(mp1) == mScan)
                    fMDown = FALSE;                 /* 'm' key went up */
         }
         else
         {
                 if (SHORT1FROMMP(mp1) & KC_CHAR)
       {
              if (InterpretChar((UCHAR)(ULONG)(mp2)))
                   {
                                 UpdateDisplay();
                   }
              else
                   {
                                   if (((UCHAR)(ULONG)(mp2)== 'm') || ((UCHAR)
                             {
                                         mScan = CHAR4FROMMP(mp1);           /
                                         fMDown = TRUE;
                             }
         }
                 }
         }
         break;


     case WM_ERASEBACKGROUND:
         if (WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MINIMIZED)
             WinValidateRect(hwnd, (PRECTL) mp2, TRUE);
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_SETFOCUS:
         if ((HWNDFROMMP(mp1)==hwndCalc) && !mp2);
             fMDown = FALSE;                        /* since we are losing foc

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }


 /*************************************************************************
     translate & interpret keys (ie. locate in logical keyboard)
  */

 BOOL InterpretChar(ch)
 register CHAR ch;
 {
     BOOL fDone;
     CHAR *chstep;

     fDone = FALSE;
     chstep = bButtonValues;
     switch (ch)
     {
     case 'n':
         ch = szPlusMinus[0];
         break;
     case 27:                        /* xlate Escape into 'c' */
         ch = 'c';
         break;
     case '\r':                      /* xlate Enter into '=' */
         ch = '=';
         break;
     }

     if (fMDown)                     /* Do memory keys */
     {
         switch (ch)
         {
         case 'c':
         case 'C':
             ch = '\274';
             break;
         case 'r':
         case 'R':
             ch = '\273';
             break;
         case '+':
             ch = '\272';
             break;
         case '-':
             ch = '\271';
             break;
         }
     }

     while (!fDone && *chstep)
     {
         if (*chstep++ == ch)
             fDone = TRUE;                /* char found in logical keyboard */
     }

     if (fDone)
     {
         Evaluate(ch);
     }

     return (fDone);
 }


 DDEML.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DDEML.C

 /****************************** Module Header ******************************\
 * Module Name: DDE.C
 *
 * DDE Manager main module - Contains all exported Dde functions.
 *
 * Created: 12/12/88 Sanford Staab
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 * 4/5/89        sanfords        removed need for hwndFrame registration param
 * 6/5/90        sanfords        Fixed callbacks so they are blocked during
 *                               timeouts.
 *                               Fixed SendDDEInit allocation bug.
 *                               Added hApp to ConvInfo structure.
 *                               Allowed QueryConvInfo() to work on server hCo
 *                               Added FindFrame() to provide an hApp for Serv
 *                               hConvs.
 * 6/14/90       sanfords        Altered hDatas so they will work when shared
 *                               between threads of the same process.  Also
 *                               added optimization to only have the hsz
 *                               in the hData for local conversations.
 * 6/21/90       sanfords        Renamed APIs to Dde....
 *                               Finished DdeAppNameServer() implementation.
 *
 \***************************************************************************/

 #include "ddemlp.h"
 #include "version.h"

 /****** Globals *******/

 HMODULE hmodDmg = 0;        /* initialized by LoadProc on DLL initialization
 PFNWP lpfnFrameWndProc = 0;             /* system Frame window procedure */

 HHEAP hheapDmg = 0;                     /* main DLL heap */

 PHATOMTBL aAtbls;
 USHORT cAtbls = 0;
 USHORT iAtblCurrent = 0;
 USHORT   cMonitor = 0;
 DOSFSRSEM FSRSemDmg;                    /* used to protect globals */
 USHORT cAtoms = 0;                      /* for debugging! */

 PAPPINFO pAppInfoList = NULL;           /* registered thread data list */
 USHORT usHugeShift;                     /* for huge segment support */
 USHORT usHugeAdd;                       /* for huge segment support */
 COUNTRYCODE syscc;

 BOOL fInSubset(PCQDATA pcqd, ULONG afCmd);

 /* PUBDOC START *\
 DLL overview:

 This DLL supports standard DDE communication on behalf of its client
 applications.  It is compatable with most existing DDE programs.  The
 API interface features object-like abstractions that allow its implementation
 on and across a variety of platforms.

 Main features:

   * HSZ manager:    Allows more efficient handling of numerous character
                     strings without the limitations of the atom manager.

   * HDATA manager:  Data container objects allow easy filling, accessing
                     and reuse of huge amounts of data and allow a
                     server to efficiently support several clients.

   * Controled initiates: Supports client to multi-server initiates and
                     allows a client application to pick and choose which
                     conversations to keep.

   * Debugging support: Allows monitoring applications to be easily written
                     and provides readable error messages.

   * Huge Segment support: Exports a useful huge segment copy function and
                     properly handles huge data transfers.

   * Synchronous communication:  Clients may use a very simple form of
                     transaction processing that requires little application
                     support.

   * Asynchronous communication:  Clients may opt to use queued transfers
                     freeing them to do other tasks in parellel with DDE.

   * Callback control:  Applications may selectively suspend DLL callbacks
                     to themselves when in time critical sections.  Selected
                     conversations can be blocked while being serviced so
                     others can be processed by the same thread.

   * registration notification: All applications using this DLL are notified
                     whenever any other application registers or unregisters
                     itself with this DLL.  This enables nameserver support
                     for DDE.

   * multiple DDE entity support:  An application can register itself with
                     any number of application names and can change what
                     application names it responds to at any time.  Each
                     registered thread is a seperate DDE entity.

   * advise loop control:  Advise loops are tracked by the DLL, however
                     server applications have complete control over
                     advise loop initiation.

   * network agent support: Special agent applications can register with this
                     DLL to represent multiple applications on other machines
                     or platforms.

 API specifications:

 Callback function:

 EXPENTRY Callback(
 HCONV hConv,        // holds server conversation handle in most cases
 HSZ hszTopic,       // holds topic hsz for transaction
 HSZ hszItem,        // holds item or application hsz for transaction
 USHORT usFormat,    // holds data format when applicable
 USHORT usType,      // holds transaction type code
 HDMGDATA hDmgData); // holds incomming data in most cases

 This is the definition of the data call-back function that an
 application must export so that the DDE can initiate interaction
 with the application when necessary.  This function is refered to in
 the DdeInitialize() call.

 The application callback function is very much like a PM window
 procedure for DDE.  The usType specifies the type of transaction being
 done.  By ANDing this parameter with XCLASS_MASK and comparing the
 result with the XCLASS_ constants, the transaction return type
 expected can be classified.

 XTYP_ constants also may contain the XTYPF_NOBLOCK flag.  The presence
 of this flag indicates that the CBR_BLOCK return value from the callback
 will not be honored by the DDE.  (see DdeEnableCallback() for more
 information on this concept.)

 The various transactions are explained below:


 -----XCLASS_NOTIFICATION class:
     These are strictly notification messages to an application.  The return
     value is ignored except in the case of CBR_BLOCK. (if the notification
     is blockable)

 XTYP_RTNPKT

    This transaction is sent to agent applications.  hDmgData contains a
    packet to send to the agent who's handle is in the hszItem parameter.

 XTYP_REGISTER

    Another server name has just been registered with the DLL application
    name server.  hszItem is set to the application name being
    registered.  If this is NULL, an application has registered itself as
    WILD.  hDmgData is set to the application handle that registered.
    This is not blockable by CBR_BLOCK because no hConv is
    associated with it.  Only if all callbacks are disabled is this
    transaction blocked.

 XTYP_UNREGISTER

    Another server application has just unregistered a name with this
    DLL.  hszItem is set to the application name being unregistered.
    hDmgData is set to the app handle of the unregistering application.
    This is not blockable by CBR_BLOCK because no hConv is associated
    with it.  Only if all callbacks are disabled is this transaction
    blocked.

 XTYP_INIT_CONFIRM

    Sent to let a server know that a conversation on application hszItem
    and Topic hszTopic has been established on hConv.  hConv uniquely
    identifies this conversation from the server's prospective.  This
    call cannot be blocked because it is part of the DDE initiate
    sequence.  This callback is generated by the results of XTYP_INIT and
    XTYP_WILDINIT callbacks.

 XTYP_TERM

    This is a notification telling a server application that a
    conversation has been terminated.  hConv is set to identify which
    conversation was terminated.

 XTYP_ADVSTOP

    This notifies a server that an advise loop is stopping.  hszTopic,
    hszItem, and usFormat identify the advise loop within hConv.

 XTYP_XFERCOMPLETE

    This notifictaion is sent to a client when an asynchronous data
    DdeClientXfer() transaction is completed.  hDmgData is the client
    queue ID of the completed transaction.  hConv is the client
    conversation handle.

 XTYP_MONITOR

    This notifies an app registered as DMGCMD_MONITOR of DDE data that is
    being transmitted.  hDmgData contains a text string representing the
    transaction suitable for printing on a terminal, file, or window.
    Note that this monitors ALL DDE communication and may be extensive.
    This call cannot be delayed by disabled callbacks.  This transaction
    is not blockable.

 XTYP_ACK

    This notifies a server that it has received an acknowledge from data
    it has sent a client.  The hConv, topic, item, and format are set
    apropriately.  LOUSHORT(hDmgData) will contain the dde flags from the
    ack.

 -----XCLASS_DATA class:

    Transactions in this class are expected to return an HDMGDATA or 0 as
    apropriate.

 XTYP_PKT

    This transaction is sent to agent applications.  hDmgData contains a
    packet to send.  hszItem contains the agent handle to send the data
    to.  hConv is set to the associated conversation handle.  The return
    packet received from the partner agent should be returned.  This
    call is blockable.

    If blocked, the conversation will be unblocked and processed by
    DdeProcessPkt() when the return packet is received.  It is a good
    idea for the agent to remember the hConv parameter in case the return
    packet does not arrive within a reasonable amount of time via
    XTYP_RTNPKT.  If an agent determines that it wishes to kill a
    conversation, it should call DdeDisconnect().

 XTYP_REQUEST
 XTYP_ADVREQ

    Data is being requested from a server application.  The function
    should create a hDmgData using the DdePutData() function and
    return it.  XTYP_ADVREQ origonates from a DdePostAdvise() call
    while XTYP_REQUEST origonates from a client data request.

 XTYP_WILDINIT

    This is asking a DDE server permission to make multiple connections
    with a specific client.

    hszItem may be the application name the client is requesting or it
    may be NULL indicating a wild application name.  hszTopic may be the
    Topic requested or NULL indicating a wild topic.  If not NULL,
    hDmgData contains a CONVCONTEXT structure.  All other parameters are
    0 or NULL.

    For local initiates, (initiates with the server application itself)
    The server should return a 0 terminated (ie hszApp = hszTopic = 0)
    array of HSZPAIR structures using DdePutData() and
    DdeAddData().  Each hsz pair represents an app/topic the server
    wishes to support.  Each created conversation will result in an
    XTYP_INIT_CONFIRM notification.  If 0 is returned, no connections are
    made with the requesting client.  This call is made even if callbacks
    are disabled due the the synchronous nature of DDE initiates.  This
    callback cannot be blocked by returning CBR_BLOCK.

    Agent applications may also process this transaction as a
    representative initiate.  The agent is expected to package this
    transaction using DdeCreateInitPkt() and broadcast it to all
    apropriate agents along its communication channel.  It then must
    collect the return packets and for each packet and call
    DdeProcessPkt().  Representative initiates are synchronous in that
    the agent cannot return from this callback until all initiate return
    packets are returned.  Representative initiates do NOT result in any
    XTYP_INIT_CONFIRM notifications to the agent.  The agent is then free
    to process this transaction for local connections as described.

 -----XCLASS_BOOL class:

    Transactions in this class expect a BOOL return of TRUE or FALSE.

 XTYP_INIT

    This is a query asking a DDE server permission to connect to a
    specific client.  hszItem is set to the Application name.  hszTopic
    is set to the topic name.  hDmgData if not NULL, contains CONVCONTEXT
    data.  All other parameters are 0 or NULL.  A TRUE return value
    allows the Dde to start up a server on the app/topic specified.
    This will result in an XTYP_INIT_CONFIRM callback.  This call is made
    even if callbacks are disabled due the the synchronous nature of DDE
    initiates.

    Agent applications may process this transaction as a representative
    transaction.  The agent is expected to package this transaction using
    DdeCreateInitPkt() and broadcast it to all apropriate agents along
    its communication channel.  It then must collect the return packets
    and for each packet call DdeProcessPkt().  This will NOT result in
    any XTYP_INIT_CONFIRM notifications to the agent.  The agent is then
    free to process this transaction for local connections.

     A FALSE return implies no local initiate permissions are granted.

 XTYP_ADVSTART

    This transaction requests permission to start a DDE advise loop with
    a server.  The hszTopic, hszItem, and usFormat identify the advise
    loop.  If FALSE is returned, the advise loop will not be started.

 -----XCLASS_FLAGS Class:

    Transactions in this class have hDmgData set.  An application should
    use the DLL Data functions to extract the data from hDmgData.  The
    return value should be the DDE fsStatus flags the app desires to
    return to the client application.  If DDE_FACK is set, DDE_FBUSY and
    DDE_FNOTPROCESSED are ignored.

    The only flags the Dde expects to be returned are:
         DDE_FACK
         DDE_FBUSY
         DDE_FNOTPROCESSED
         any DDE_APPSTATUS bits

    All other bits will be stripped out by the Dde before sending an
    ack message.

    A 0 return is equivalent to DDE_NOTPROCESSED.

 XTYP_EXEC

    hDmgData contains an execute string from a client.  hConv, hszTopic,
    hszItem are set.  If the WM_DDE_EXECUTE message received had the same
    string for the itemname as for the data, hszItem will be 0L.  This
    provides for EXCEL EXECUTE compatibility without requireing the
    creation of an HSZ for the data string.  Applications are advised to
    ignore the hszItem parameter for execute transactions since newer DDE
    specifications ignore this value.

 XTYP_POKE

    Similar to XTYP_EXEC but hDmgData contains data poked to the server.

 XTYP_ADVDATA - advise data for a client!

    Note that XTYP_ADVDATA is for advise loop data intended for the
    CLIENT not the server.  If the advise loop in progress is of the
    NODATA type, hDmgData will be 0.

 -----Agent Transfers:

    A DDE agent application is one which registers itself with the
    DMGCMD_AGENT flag.  Agent applications represent any number of other
    applications across its communications channel.  Agent applications
    are only allowed to communicate locally with other non-agent
    applications.  This prevents communication loops from forming across
    communication channels.  Any number of agents may register with the
    DLL but each agent should represent a different communication
    channel, one which is orthogonal to all other agents.  It is the
    users responsability to only start up orthogonal agents.

    Agents are responsible for handling and updating any DDE nameservers
    associated with their communicaton channels.  Since agent
    applications can converse directly with non-agent applications, they
    can set up advise loops on the SysTopic/Topics items of local
    applications to update the nameserver for the communication channel
    if they wish to support DDE topics.  This may be impossible with some
    DDE applications which either do not support the SysTopic/Topics item
    or which have an unenumerable set of topics they support.  For
    application name servers the DdeAppNameServer() function is
    provided to give agents a local application name server from which to
    draw on.

    In general, an agent administers two classes of conversations.  One
    is direct conversations with itself and local non-agent applications.
    These transactions would be handled by the agent exactly like any
    non-agent application would handle them.

    The other class is representative conversations which the agent
    passes over its communication channel.  In general, representative
    conversation callbacks to the agent are only XTYP_PKT or XTYP_RTNPKT
    type transactions or specially handled initiate transactions.

    Agent applications are responsible for providing a unique ULONG agent
    handle for every agent it is communicating with on its communication
    channel.  The handle is provided by the agent whenever it calls
    DdeProcessPkt().  Agent handles need not be global to the channel
    since only the agent that created the handle will be expected to use
    it.  Agent handles should not change over the life of a conversation.

    Should it be necessary to allow agents to alter or convert packet
    data, the format of the packets can be documented later.

    To show how an agent would handle its callback function, the
    following pseudo code is offered as a model:

 ReceivePkt(pBits, cb, hAgentFrom)    \\ gets called when a packet arrives
 {
     hPkt = DdePutData(pBits, cb, 0, 0, 0, 0);
     if (hDmgData = ProcessPkt(hPkt, hAgentFrom))
         PassPkt(hDmgData, hAgentFrom);  \\ agent function to send pkt
 }

 AgentCallback(hConv, hszTopic, hszItem, usFormat, usType, hDmgData)
 {
     switch (usType) {
     case XTYP_INIT:
     case XTYP_WILDINIT:
         \\
         \\ process representative initiates
         \\
         QueryInterestedAgents(hszApp, hszTopic, pAgents);
         hDmgData = DdeCreateInitPkt(hszTopic, hszItem, hDmgData);
         BroadcastPkt(hDmgData, pAgents);
         \\
         \\ agent blocks here till all are in or timeout.
         \\ Packets get sent to ReceivePkt()
         \\
         CollectRtnPkts(pAgents);
         \\
         \\ now agent does his own processing of local inits.
         \\ retval == 0 if not interested.
         \\
         return(retval);
         break;

     case XTYP_RTNPKT:
         RemoveFromDeadCheckQ(hConv);
         PassPkt(hDmgData, hszItem); \\ hDmgData==Pkt, hszItem==hAgentTo
         return(0);
         break;

     case XTYP_PKT:
         if (FindIgnoreList(hConv) { \\ was this unblocked due to no rtn pkt?
             RemoveFromIgnoreList(hConv);
             return(0);  \\ rtn pkt failure.
         }
         if (!PassPkt(hDmgData, hszItem))  \\ hDmgData==Pkt, hszItem==hAgentTo
             return(0);  \\ packet send failure.
         AddToDeadCheckQ(hConv, timenow());
         return(CBR_BLOCK);  \\ will be unblocked and handled by ProcessPkt()
         break;

     case XTYP_REGISTER:
     case XTYP_UNREGISTER:
         \\
         \\ agent updates its communications name server.
         \\
         return(0);
         break;

     default:
         \\
         \\ the rest would reference local conversatoins that the agent
         \\ is maintaining.
         \\
         break;
     }
 }

 UnblockDeadTransaction(hConv) \\ called when no rtn pkt for hConv has been
                               \\ received for a long time.
 {
     RemoveFromDeadCheckQ(hConv);
     AddToIgnoreList(hConv);
     DdeEnableCallback(CBK_ENABLE, hConv);
 }

 \* PUBDOC END */


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * USHORT EXPENTRY DdeInitialize(pfnCallback, afCmd, ulRes)
 * PFNCALLBACK pfnCallback;  // address to application callback function
 * ULONG afCmd;              // registration command flags
 * ULONG ulRes;              // currently reserved, must be 0L.
 *
 *     This API is used to initialize the DDEML for an application thread.
 *
 *     afCmd - is a set of DMGCMD_ flags for special initialization instructio
 *
 *     DMGCMD_AGENT
 *         The registering application represents more than one DDE applicatio
 *         Agents are never allowed to establish a conversation with
 *         another agent.  See Agent Transactions.
 *
 *     DMGCMD_MONITOR
 *
 *         This defines the registered application as a DDE transaction
 *         monitor.  This is primarily used for debugging DDE
 *         applications.  A monitoring application will have its Callback
 *         function called every time a DDE message is sent.
 *
 *         This flag is exclusive of all others.  No other flags should be
 *         or'ed in with this one.
 *
 *    DMGCMD_CLIENTONLY
 *        This should be specified when the application only intends to
 *        be a DDE client.  This reduces the resource consumption of the DLL.
 *
 *     Registration is on a per-thread basis.  Thus a multi-threaded applicati
 *     could register several threads as seperate DDE entities.
 *
 *     returns any applicable DMGERR_ error code or 0 on success.
 *
 *     Most other DLL APIs will fail if the calling thread has not called this
 *
 * PUBDOC END
 *
 * Registration causes the following windows to be created:
 *
 * HWND_OBJECT
 *   hwndDmg(s)
 *       hwndClient(s)
 *   hwndTopicServer(s)
 *       hwndServer(s)
 *   hwndMonitor(s)
 * HWND_DESKTOP
 *   hwndFrame(s)
 *
 *   See api.doc file for usage info.
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 USHORT EXPENTRY DdeInitialize(pfnCallback, afCmd, ulRes)
 PFNCALLBACK pfnCallback;
 ULONG afCmd;
 ULONG ulRes;
 {
     if (ulRes != 0L || CheckSel(SELECTOROF(pfnCallback)) == 0)
         return(DMGERR_INVALIDPARAMETER);

     return(Register(pfnCallback, afCmd, ulRes, FALSE));
 }



 USHORT EXPENTRY Register(pfnCallback, afCmd, ulRes, f32bit)
 PFNCALLBACK pfnCallback;
 ULONG afCmd;
 ULONG ulRes;
 BOOL f32bit;    /* set if calling app is a 32bit app. */
 {
     BOOL        fInit;
     PAPPINFO    pai = 0L, paiT;
     PIDINFO     pidInfo;
     USHORT      usRet = DMGERR_PMWIN_ERROR;
     ULONG       ctlFlags;
     CLASSINFO   ci;
     USHORT      cb;

     UNUSED ulRes;

     SemEnter();
     if (fInit = (hheapDmg == 0L)) {
         /*
          * First time only
          */
         syscc.codepage = syscc.country = 0;
         DosGetCtryInfo(sizeof(COUNTRYCODE), &syscc, (PCOUNTRYINFO)&syscc, &cb
         if (DosGetHugeShift(&usHugeShift))
             goto Abort;
         usHugeAdd = (1 << usHugeShift) - 1;
         if (!(hheapDmg = MyCreateHeap(0, 4096, 0, 0, 0, HEAPFLAGS)))
             goto Abort;
         if (!WinQueryClassInfo(DMGHAB, WC_FRAME, &ci))
             goto Abort;
         lpfnFrameWndProc = ci.pfnWindowProc;
         if (!AddAtomTable(TRUE))
             goto Abort;
     } else {

         if ((pai = GetCurrentAppInfo(FALSE)) != NULL) {
             /*
              * re-registration
              */
             return(DMGERR_DLL_USAGE);
         }

         /*
          * share the main heap with this process.
          */
         if (DosGetSeg(SELECTOROF(hheapDmg))) {
             SemLeave();
             return(DMGERR_PMWIN_ERROR);
         }
     }


     if (DosGetPID(&pidInfo))
         goto Abort;

     if (!(pai = (PAPPINFO)FarAllocMem(hheapDmg, sizeof(APPINFO))))
         goto Abort;

     if (!(pai->hheapApp = MyCreateHeap(0, 4096, 0, 0, 0, HEAPFLAGS))) {
         FarFreeMem(hheapDmg, pai, sizeof(APPINFO));
         pai = 0L;
         goto Abort;
     }

     pai->pAppNamePile = NULL;   /* responds to nothing */
     pai->pSvrTopicList = CreateLst(pai->hheapApp, sizeof(HWNDHSZLI));
     pai->pHDataPile = CreatePile(pai->hheapApp, sizeof(HDMGDATA), 8);
     pai->afCmd = (USHORT)afCmd | (f32bit ? DMGCMD_32BIT : 0);
     pai->hwndDmg =
     pai->hwndFrame =
     pai->hwndMonitor =
     pai->hwndTimer = 0;
     pai->pid = pidInfo.pid;
     pai->tid = pidInfo.tid;
     pai->pfnCallback = pfnCallback;
     pai->cInCallback = 0;
     pai->LastError = DMGERR_NO_ERROR;
     pai->fEnableCB = TRUE;
     pai->plstCB = CreateLst(pai->hheapApp, sizeof(CBLI));
     pai->plstCBExceptions = NULL;

     /*
      * make nextThread link.
      */
     paiT = pAppInfoList;
     while (paiT && paiT->pid != pai->pid) {
         paiT = paiT->next;
     }
     pai->nextThread = paiT; /* paiT is NULL or of the same process */

     if (paiT) {
         while (paiT->nextThread->tid != pai->nextThread->tid) {
             paiT = paiT->nextThread;
         }
         paiT->nextThread = pai;
     } else {
         /*
          * We must reregister each class for each process that invokes this
          * DLL because we can't register public classes unless we are the
          * shell.
          * Since pai->nextThread is NULL, this is a new process.
          */
         WinRegisterClass(0, SZCLIENTCLASS, ClientWndProc, 0L, 4);
         WinRegisterClass(0, SZSERVERCLASS, ServerWndProc, 0L, 4);
         WinRegisterClass(0, SZDMGCLASS, DmgWndProc, 0L, 4);
         WinRegisterClass(0, SZDEFCLASS, WinDefWindowProc, 0L, 4);
     }

     pai->next = pAppInfoList;
     pAppInfoList = pai;

     if ((pai->hwndDmg = WinCreateWindow(HWND_OBJECT, SZDMGCLASS, "", 0L,
             0, 0, 0, 0, (HWND)NULL, HWND_BOTTOM, WID_APPROOT, 0L, 0L)) == 0L)
         goto Abort;
     }

     if (pai->afCmd & DMGCMD_MONITOR) {
         WinRegisterClass(0, SZMONITORCLASS, MonitorWndProc, 0L, 4);
         if ((pai->hwndMonitor = WinCreateWindow(HWND_OBJECT, SZMONITORCLASS,
                 0L, 0, 0, 0, 0, (HWND)NULL, HWND_BOTTOM, WID_MONITOR, 0L, 0L)
                 == 0L) {
             goto Abort;
         }
         if (++cMonitor) {
             WinSetHook(DMGHAB, NULL, HK_INPUT, (PFN)DdePostHookProc, hmodDmg)
             WinSetHook(DMGHAB, NULL, HK_SENDMSG, (PFN)DdeSendHookProc, hmodDm
         }
     }

     /*
      * create an invisible top-level frame for initiates. (if server ok)
      */
     usRet = DMGERR_PMWIN_ERROR;
     if (!(afCmd & DMGCMD_CLIENTONLY)) {
         ctlFlags = 0;
         if ((pai->hwndFrame = WinCreateStdWindow(HWND_DESKTOP, 0L, &ctlFlags,
                 (PSZ)NULL, "", 0L, (HMODULE)NULL, 0, (PHWND)NULL)) == (HWND)N
             goto Abort;
         WinSubclassWindow(pai->hwndFrame, subframeWndProc);
     }

     DosExitList(EXLST_ADD, (PFNEXITLIST)ExlstAbort);

     SemLeave();

     return(DMGERR_NO_ERROR);

 Abort:
     SemLeave();

     if (pai)
         DdeUninitialize();
     else if (fInit && hheapDmg)
         hheapDmg = MyDestroyHeap(hheapDmg);
     return(usRet);
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeUninitialize(void);
 *     This uninitializes an application thread from the DDEML.
 *     All DLL resources associated with the application are destroyed.
 *     Most other APIs will fail if called after this API by the same thread.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeUninitialize()
 {
     PAPPINFO pai, paiT;
     PMYDDES pmyddes;
     PIDINFO pi;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(FALSE);

     DosExitList(EXLST_REMOVE, (PFNEXITLIST)ExlstAbort);

     /*
      * get us out of the semaphore!
      * !!! NOTE: semaphore tid id -1 during exitlist processing!
      */
     DosGetPID(&pi);
     if (FSRSemDmg.cUsage > 0 && FSRSemDmg.pid == pi.pid &&
             (FSRSemDmg.tid == pi.tid || FSRSemDmg.tid == -1)) {
         while (FSRSemDmg.cUsage) {
             SemLeave();
         }
     }

     if (pai->hwndTimer)
         WinSendMsg(pai->hwndTimer, WM_TIMER, MPFROMSHORT(TID_ABORT), 0L);

     if (pai->hwndMonitor) {
         DestroyWindow(pai->hwndMonitor);
         if (!--cMonitor) {
             WinReleaseHook(DMGHAB, NULL, HK_INPUT, (PFN)DdePostHookProc, hmod
             WinReleaseHook(DMGHAB, NULL, HK_SENDMSG, (PFN)DdeSendHookProc, hm
         }
     }

     /*
      * inform others of DeRegistration
      */
     if (pai->pAppNamePile != NULL)
         DdeAppNameServer(NULL, ANS_UNREGISTER);

     UnlinkAppInfo(pai);

     DestroyWindow(pai->hwndDmg);
     DestroyWindow(pai->hwndFrame);
     DestroyHwndHszList(pai->pSvrTopicList);
     while (PopPileSubitem(pai->pHDataPile, (PBYTE)&pmyddes)) {
         if (CheckSel(SELECTOROF(pmyddes)) > sizeof(MYDDES) &&
                 pmyddes->magic == MYDDESMAGIC &&
                 pmyddes->pai == pai) {
             pmyddes->fs &= ~HDATA_APPOWNED;
         }
         FreeData(pmyddes, pai);
     }
     DestroyPile(pai->pHDataPile);
     if (pai->nextThread) {
         paiT = pai;
         while (paiT->nextThread != pai) {
             paiT = paiT->nextThread;
         }
         paiT->nextThread = pai->nextThread;
         if (paiT->nextThread == paiT) {
             paiT->nextThread = NULL;
         }
     }
     MyDestroyHeap(pai->hheapApp);

     DestroyPile(pai->pAppNamePile);
     FarFreeMem(hheapDmg, (PBYTE)pai, sizeof(APPINFO));

     if (pAppInfoList == NULL) {     /* last guy out? - turn the lights out. *
         while (cAtbls--)
             WinDestroyAtomTable(aAtbls[cAtbls]);
         hheapDmg = MyDestroyHeap(hheapDmg);
     } else
         DosFreeSeg(SELECTOROF(hheapDmg));

     SemCheckOut();

     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HCONVLIST EXPENTRY DdeBeginEnumServers(
 * HSZ hszAppName,    // app name to connect to, NULL is wild.
 * HSZ hszTopic,      // topic name to connect to, NULL is wild.
 * HCONV hConvList,   // previous hConvList for reenumeration, NULL for initia
 * PCONVCONTEXT pCC,  // language info or NULL for system default.
 * HAPP hApp);        // target application handle or NULL for broadcast init.
 *
 *   hszAppName - the DDE application name to connect to - may be 0 for wild.
 *   hszTopic - the DDE topic name to connect to - may be 0 for wild.
 *   hConvList - The conversation list handle to use for reenumeration.
 *       If this is 0, a new hConvList is created.
 *   pCC - pointer to CONVCONTEXT structure which provides conversation
 *       information needed for international support.  All DDEFMT_TEXT
 *       strings within any conversation started by this call should use
 *       the codepage referenced in this structure.
 *       If NULL is given, the current system values are used.
 *   hApp - if not NULL, this directs initiates to only be sent to hApp.
 *
 *       This routine connects all available conversations on the given
 *       app/topic pair.  Hsz values of 0 indicate wild names.  On reenumerati
 *       old hConv's are kept and any new ones created are added to the
 *       list.  Duplicate connections are avoided where possible.  A
 *       duplicate connection is one which is to the same process and
 *       thread on the same application/topic names.  If hApp is provided,
 *       initiates are given only to that application.
 *       Reenumeration is primarily intended as a response
 *       to registration of a new app name to the system.  Reenumeration
 *       also removes any terminated conversations from the list.
 *
 *   returns NULL on failure, hConvList on success.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 HCONVLIST EXPENTRY DdeBeginEnumServers(hszAppName, hszTopic, hConvList,
         pCC, hApp)
 HSZ hszAppName;
 HSZ hszTopic;
 HWND hConvList;
 PCONVCONTEXT pCC;
 HAPP hApp;
 {
     PAPPINFO            pai;
     HCONV               hConv, hConvNext, hConvNew;
     HCONVLIST           hConvListNew;
     PCLIENTINFO         pciOld, pciNew;
     PID                 pidOld, pidNew;
     TID                 tidOld, tidNew;

     if ((pai = GetCurrentAppInfo(TRUE)) == 0)
         return(0);

     /*
      * destroy any dead old clients
      */
     if (hConvList) {
         hConv = WinQueryWindow(hConvList, QW_TOP, FALSE);
         while (hConv != NULL) {
             hConvNext = WinQueryWindow(hConv, QW_NEXT, FALSE);
             if (!((USHORT)WinSendMsg(hConv, UM_QUERY, MPFROMSHORT(Q_STATUS),
                     ST_CONNECTED))
                 WinDestroyWindow(hConv);
             hConv = hConvNext;
         }
     }

     if ((hConvListNew = WinCreateWindow(pai->hwndDmg, SZDEFCLASS, "", 0L,
             0, 0, 0, 0, (HWND)NULL, HWND_BOTTOM, WID_CLROOT, 0L, 0L)) == NULL
         pai->LastError = DMGERR_PMWIN_ERROR;
         return(0L);
     }

     hConvNew = GetDDEClientWindow(hConvListNew, (HWND)hApp, NULL, hszAppName,
             hszTopic, pCC);

     /*
      * If no new hConvs created, quit now.
      */
     if (hConvNew == NULL) {
         if (hConvList && WinQueryWindow(hConvList, QW_TOP, FALSE) == NULL) {
             DestroyWindow(hConvList);
             hConvList = NULL;
         }
         if (hConvList == NULL)
             pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
         return(hConvList);
     }

     /*
      * remove any new ones that duplicate old existing ones
      */
     if (hConvList && (hConv = WinQueryWindow(hConvList, QW_TOP, FALSE))) {
         while (hConv) {
             hConvNext = WinQueryWindow(hConv, QW_NEXT, FALSE);
             pciOld = (PCLIENTINFO)WinQueryWindowULong(hConv, QWL_USER);
             if (!WinIsWindow(DMGHAB, pciOld->ci.hwndPartner)) {
                 WinDestroyWindow(hConv);
                 hConv = hConvNext;
                 continue;
             }
             WinQueryWindowProcess(pciOld->ci.hwndPartner, &pidOld, &tidOld);
             /*
              * destroy any new clients that are duplicates of the old ones.
              */
             hConvNew = WinQueryWindow(hConvListNew, QW_TOP, FALSE);
             while (hConvNew) {
                 hConvNext = WinQueryWindow(hConvNew, QW_NEXT, FALSE);
                 pciNew = (PCLIENTINFO)WinQueryWindowULong(hConvNew, QWL_USER)
                 WinQueryWindowProcess(pciNew->ci.hwndPartner, &pidNew, &tidNe
                 if (pciOld->ci.hszServerApp == pciNew->ci.hszServerApp &&
                         pciOld->ci.hszTopic == pciNew->ci.hszTopic &&
                         pidOld == pidNew &&
                         tidOld == tidNew) {
                     /*
                      * assume same app, same topic, same process, same thread
                      * is a duplicate.
                      */
                     WinDestroyWindow(hConvNew);
                 }
                 hConvNew = hConvNext;
             }
             /*
              * move the unique old client to the new list
              */
             WinSetParent(hConv, hConvListNew, FALSE);
             hConv = hConvNext;
         }
         WinDestroyWindow(hConvList);
     }

     /*
      * If none are left, fail because no conversations were established.
      */
     if (WinQueryWindow(hConvListNew, QW_TOP, FALSE) == NULL) {
         DestroyWindow(hConvListNew);
         pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
         return(NULL);
     } else {
         return(hConvListNew);
     }
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HCONV EXPENTRY DdeGetNextServer(
 * HCONVLIST hConvList,  // conversation list being traversed
 * HCONV hConvPrev)      // previous conversation extracted or NULL for first
 *
 * hConvList - handle of conversation list returned by DdeBeginEnumServers().
 * hConvPrev - previous hConv returned by this API or 0 to start from the top
 *   of hConvList.
 *
 * This API returns the next conversation handle associated with hConvList.
 * A 0 is returned if hConvPrev was the last conversation or if hConvList
 * has no active conversations within it.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 HCONV EXPENTRY DdeGetNextServer(hConvList, hConvPrev)
 HCONVLIST hConvList;
 HCONV hConvPrev;
 {
     if (!WinIsWindow(DMGHAB, hConvList))
         return(NULL);
     if (hConvPrev == NULL)
         return(WinQueryWindow(hConvList, QW_TOP, FALSE));
     else
         return(WinQueryWindow(hConvPrev, QW_NEXT, FALSE));
 }




 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeEndEnumServers(hConvList)
 * HCONVLIST hConvList;  // conversation list to destroy.
 *
 * hConvList - a conversation list handle returned by DdeBeginEnumServers().
 *
 * This API destroys hConvList and terminates all conversations associated
 * with it.  If an application wishes to save selected conversations within
 * hConvList, it should call DdeDisconnect() on all hConv's it does not
 * want to use and not call this API.
 *
 * PUBDOC END
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeEndEnumServers(hConvList)
 HCONVLIST hConvList;
 {
     PAPPINFO pai;
     HCONV hConv, hConvNext;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(FALSE);
     if (WinIsWindow(DMGHAB, hConvList)) {
         hConv = WinQueryWindow(hConvList, QW_TOP, FALSE);
         while (hConv != NULL) {
             hConvNext = WinQueryWindow(hConv, QW_NEXT, FALSE);
             DestroyWindow(hConv);
             hConv = hConvNext;
         }
         DestroyWindow(hConvList);
     }
     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HCONV EXPENTRY DdeConnect(hszAppName, hszTopic, pCC, hApp)
 * HSZ hszAppName;   // app name to connect to, NULL is wild.
 * HSZ hszTopic;     // topic name to connect to, NULL is wild.
 * PCONVCONTEXT pCC; // language information or NULL for sys default.
 * HAPP hApp;        // target application or NULL for broadcast.
 *
 * hszAppName - DDE application name to connect to.
 * hszTopic - DDE Topic name to connect to.
 * pCC - CONVCONTEXT information pertinant to this conversation.
 *       If NULL, the current system information is used.
 * hApp - if not NULL, directs connection to a specific app.
 *
 * returns - the conversation handle of the connected conversation or 0 on err
 *
 * This function allows the simpler aproach of allowing a client to
 * talk to the first server it finds on a topic.  It is most efficient when
 * an hApp is provided.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/16/88    Sanfords
 \***************************************************************************/
 HCONV EXPENTRY DdeConnect(hszAppName, hszTopic, pCC, hApp)
 HSZ hszAppName;
 HSZ hszTopic;
 PCONVCONTEXT pCC;
 HAPP hApp;
 {
     PAPPINFO pai;
     HCONV hConv;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);

     hConv = GetDDEClientWindow(pai->hwndDmg, NULL, (HWND)hApp, hszAppName,
             hszTopic, pCC);

     if (hConv == 0)
         pai->LastError = DMGERR_NO_CONV_ESTABLISHED;

     return(hConv);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeDisconnect(hConv)
 * hConv; // conversation handle of conversation to terminate.
 *
 * This API terminates a conversation started by either DdeConnect() or
 * DdeBeginEnumServers().  hConv becomes invalid after this call.
 *
 * If hConv is a server conversation, any transactions for that conversation
 * found on the server callback queue will be deleted prior to terminating
 * the conversation.
 *
 * If hConv is a client conversation, any transactions on the Client Queue
 * are purged before termination.
 *
 * Note that client conversations that are terminated from the server end
 * go into a dormant state but are still available so that DdeQueryConvInfo()
 * can be used to determine why a conversation is not working.
 * Server conversations will destroy themselves if terminated from a client.
 *
 * returns fSuccess
 *
 * PUBDOC END
 * History:
 *   Created     12/16/88    Sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeDisconnect(hConv)
 HCONV hConv;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(FALSE);

     if (!WinIsWindow(DMGHAB, hConv)) {
         pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
         return(FALSE);
     }
     SemCheckOut();
     return((BOOL)WinSendMsg(hConv, UMCL_TERMINATE, 0L, 0L));
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeQueryConvInfo(hConv, pConvInfo, idXfer)
 * HCONV hConv;          // conversation hand to get info on.
 * PCONVINFO pConvInfo;  // structure to hold info.
 * ULONG idXfer;         // transaction ID if async, QID_SYNC if not.
 *
 * hConv - conversation handle of a conversation to query.
 * pConvInfo - pointer to CONVINFO structure.
 * idXfer - Should be a QID_ constant or an ID returned from DdeCheckQueue().
 *       if id is QID_SYNC, then the synchronous conversation state is returne
 *
 * returns - fSuccess.  The CONVINFO structure is filled in with the
 *     conversation's status on success.
 *
 * Note that a client conversation may have several transactions in progress
 * at the same time.  idXfer is used to choose which transaction to refer to.
 *
 * hConv may be a client or server conversation handle.  Server conversation
 * handles ignore idXfer.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeQueryConvInfo(hConv, pConvInfo, idXfer)
 HCONV hConv;
 PCONVINFO pConvInfo;
 ULONG idXfer;
 {
     PCLIENTINFO pci;
     PAPPINFO pai;
     PXADATA pxad;
     PCQDATA pqd;
     BOOL fClient;

     if ((pai = GetCurrentAppInfo(FALSE)) == 0)
         return(FALSE);

     SemCheckOut();

     /*
      * note that pci may actually be a psi if fClient is false.  Since
      * the common info portions are identical, we don't care except
      * where data is extracted from non-common portions.
      */
     if (!WinIsWindow(DMGHAB, hConv) ||
             !(pci = (PCLIENTINFO)WinSendMsg(hConv, UM_QUERY, (MPARAM)Q_ALL, 0
             !WinIsWindow(DMGHAB, pci->ci.hwndPartner)) {
         pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
         return(FALSE);
     }

     fClient = (BOOL)WinSendMsg(hConv, UM_QUERY, (MPARAM)Q_CLIENT, 0L);

     if (idXfer == QID_SYNC || !fClient) {
         pxad = &pci->ci.xad;
     } else {
         if (pci->pQ != NULL &&
                 (pqd = (PCQDATA)Findqi(pci->pQ, idXfer))) {
             pxad = &pqd->xad;
         } else {
             pai->LastError = DMGERR_UNFOUND_QUEUE_ID;
             return(FALSE);
         }
     }
     SemEnter();
     pConvInfo->cb = sizeof(CONVINFO);
     pConvInfo->hConvPartner = IsDdeWindow(pci->ci.hwndPartner);
     pConvInfo->hszAppName = pci->ci.hszServerApp;
     pConvInfo->hszAppPartner = fClient ? pci->ci.hszServerApp : 0;
     pConvInfo->hszTopic = pci->ci.hszTopic;
     pConvInfo->hAgent = pci->ci.hAgent;
     pConvInfo->hApp = pci->ci.hwndFrame;
     if (fClient) {
         pConvInfo->hszItem = pxad->pXferInfo->hszItem;
         pConvInfo->usFmt = pxad->pXferInfo->usFmt;
         pConvInfo->usType = pxad->pXferInfo->usType;
         pConvInfo->usConvst = pxad->state;
         pConvInfo->LastError = pxad->LastError;
     } else {
         pConvInfo->hszItem = NULL;
         pConvInfo->usFmt = 0;
         pConvInfo->usType = 0;
         pConvInfo->usConvst = pci->ci.xad.state;
         pConvInfo->LastError = pci->ci.pai->LastError;
     }
     pConvInfo->usStatus = pci->ci.fs;
     pConvInfo->fsContext = pci->ci.cc.fsContext;
     pConvInfo->idCountry = pci->ci.cc.idCountry;
     pConvInfo->usCodepage = pci->ci.cc.usCodepage;
     SemLeave();
     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdePostAdvise(hszTopic, hszItem)
 * HSZ hszTopic;     // topic of changed data, NULL is all topics
 * HSZ hszItem;      // item of changed data, NULL is all items
 *
 * Causes any clients who have advise loops running on the topic/item name
 * specified to receive the apropriate data messages they require.
 *
 * This should be called by a server application anytime data associated with
 * a particular topic/item changes.  This call results in XTYP_ADVREQ
 * callbacks being generated.
 *
 * hszTopic and/or hszItem may be NULL if all topics or items are to be update
 * This will result in callbacks for all active advise loops that fit the
 * hszTopic/hszItem pair.
 *
 * The API is intended for SERVERS only!
 *
 * PUBDOC END
 * History:
 *   Created     12/16/88    Sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdePostAdvise(hszTopic, hszItem)
 HSZ hszTopic;
 HSZ hszItem;
 {
     PAPPINFO pai;
     HWND hwndTopic;
     HWND hwndSvr;

     // LATER - add wild hsz support

     if (((pai = GetCurrentAppInfo(TRUE)) == 0))
         return(FALSE);

     if (pai->afCmd & DMGCMD_CLIENTONLY) {
         pai->LastError = DMGERR_DLL_USAGE;
         return(FALSE);
     }

     if ((hwndTopic = HwndFromHsz((HSZ)hszTopic, pai->pSvrTopicList)) == 0)
         return(TRUE);

     hwndSvr = WinQueryWindow(hwndTopic, QW_TOP, FALSE);
     while (hwndSvr) {
         WinPostMsg(hwndSvr, UMSR_POSTADVISE, MPFROMSHORT(hszItem), 0L);
         hwndSvr = WinQueryWindow(hwndSvr, QW_NEXT, FALSE);
     }

     return(TRUE);
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HDMGDATA EXPENTRY DdeClientXfer(pSrc, cb, hConv, hszItem, usFmt,
 *         usType, ulTimeout, pidXfer)
 * PBYTE pSrc;       // data source, or NULL for non-data cases.
 * ULONG cb;         // data size or 0 for non-data cases.
 * HCONV hConv;      // associated conversation handle
 * HSZ hszItem;      // item of transaction
 * USHORT usFmt;     // format for transaction
 * USHORT usType;    // transaction type code
 * ULONG ulTimeout;  // timeout for synchronous, TIMEOUT_ASSYNC otherwise.
 * PULONG pidXfer;   // OUTPUT: assync transfer id, NULL for no output.
 *
 * This API initiates a transaction from a client to the server connected
 * via the conversation specified by hConv.
 *
 * Currently usType may be:
 *     XTYP_REQUEST
 *     XTYP_POKE
 *     XTYP_EXEC
 *     XTYP_ADVSTART
 *     XTYP_ADVSTART | XTYPF_NODATA
 *     XTYP_ADVSTART | XTYPF_ACKREQ
 *     XTYP_ADVSTART | XTYPF_NODATA | XTYPF_ACKREQ
 *     XTYP_ADVSTOP
 *
 * ulTimeout specifies the maximum time to wait for a response in miliseconds
 * and applies to synchronous transactions only.
 * if ulTimeout is TIMEOUT_ASSYNC, then the transfer is asynchronous and
 * pidXfer may point to where to place the client transaction queue item
 * ID created by this request.
 * pidXfer may be NULL if no ID is desired.
 *
 * If usType is XTYP_REQUEST, synchronous transfers return a valid hDmgData
 * on success which holds the data received from the request.
 *
 * if usType is XTYP_EXEC and hszItem==NULL (wild) and usFmt==DDEFMT_TEXT,
 * the item name will be changed to the same as the text data.
 * This allows for EXCEL and porthole WINDOWS compatability
 * for XTYP_EXEC transactions.  It is suggested that applications always set
 * hszItem to NULL for XTYP_EXEC transactions and that servers ignore the
 * hszItem perameter in their callback for execute transactions.
 *
 * returns hDmgData or ACK DDE flags on Success, 0 on failure.
 *
 * Note: the hDmgData passed in by this call is only valid for the duration
 * of the callback.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 HDMGDATA EXPENTRY DdeClientXfer(pSrc, cb, hConv, hszItem, usFmt,
         usType, ulTimeout, pidXfer)
 PBYTE pSrc;
 ULONG cb;
 HCONV hConv;
 HSZ hszItem;
 USHORT usFmt;
 USHORT usType;
 ULONG ulTimeout;
 PULONG pidXfer;
 {
     PAPPINFO pai;
     XFERINFO xi;
     HDMGDATA hDmgData;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);

     if (!WinIsWindow(DMGHAB, hConv)) {
         pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
         return(0);
     }

     SemCheckOut();

     switch (usType) {
     case XTYP_REQUEST:
     case XTYP_POKE:
     case XTYP_EXEC:
     case XTYP_ADVSTART:
     case XTYP_ADVSTART | XTYPF_NODATA:
     case XTYP_ADVSTART | XTYPF_ACKREQ:
     case XTYP_ADVSTART | XTYPF_NODATA | XTYPF_ACKREQ:
     case XTYP_ADVSTOP:
         xi.pidXfer = pidXfer;
         xi.ulTimeout = ulTimeout;
         xi.usType = usType;
         xi.usFmt = usFmt;
         xi.hszItem = hszItem;
         xi.hConv = hConv;
         xi.cb = cb;
         xi.pData = pSrc;
         hDmgData = (HDMGDATA)WinSendMsg(hConv, UMCL_XFER, (MPARAM)&xi, 0L);
         if (ulTimeout == TIMEOUT_ASYNC) {
             /*
              * Increment the count of hszItem incase the app frees it on
              * return.  This will be decremented when the client Queue
              * entry is removed.
              */
             IncHszCount(hszItem);
         }

         /*
          * add the hDmgData to the client's list of handles he needs
          * to eventually free.
          */
         if ((usType & XCLASS_DATA) && (CheckSel(SELECTOROF(hDmgData)))) {
             AddPileItem(pai->pHDataPile, (PBYTE)&hDmgData, CmpULONG);
             if (((PMYDDES)hDmgData)->magic == MYDDESMAGIC) {
                 ((PMYDDES)hDmgData)->fs |= HDATA_READONLY;
             }
         }

         return(hDmgData);
     }
     pai->LastError = DMGERR_INVALIDPARAMETER;
     return(0);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * USHORT EXPENTRY DdeGetLastError(void)
 *
 * This API returns the most recent error registered by the DDE manager for
 * the current thread.  This should be called anytime a DDE manager API
 * returns in a failed state.
 *
 * returns an error code which corresponds to a DMGERR_ constant found in
 * ddeml.h.  This error code may be passed on to DdePostError() to
 * show the user the reason for the error or to DdeGetErrorString() to convert
 * the error code into an apropriate string.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 USHORT EXPENTRY DdeGetLastError(void)
 {
     PAPPINFO pai;
     SHORT err = DMGERR_DLL_NOT_INITIALIZED;

     pai = GetCurrentAppInfo(FALSE);

     if (pai) {
         err = pai->LastError;
         pai->LastError = DMGERR_NO_ERROR;
     }
     return(err);
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * void EXPENTRY DdePostError(err)
 * ULONG err;    // error code to post.
 *
 * This API puts up a message box describing the error who's code was
 * passed in.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/20/88    sanfords
 \***************************************************************************/
 void EXPENTRY DdePostError(err)
 USHORT err;
 {
     char szError[MAX_ERRSTR + 1];

     if (err < DMGERR_FIRST || err > DMGERR_LAST)
         return;
     WinLoadString(DMGHAB, hmodDmg, err, MAX_ERRSTR + 1, szError);
     WinMessageBox(HWND_DESKTOP, NULL, szError, SZERRCAPTION, 0,
             MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * USHORT EXPENTRY DdeGetErrorString(err, cbMax, psz)
 * USHORT err;       // error code to convert
 * USHORT cbMax;     // size of string buffer provided by caller
 * PSZ psz;          // string buffer address.
 *
 * This function fills psz with the error string referenced by err of up
 * to cbMax characters.  All error strings are <= MAX_ERRSTR in length.
 * (not counting the NULL terminator.)
 *
 * returns length of copied string without NULL terminator or 0 on failure.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/20/88    sanfords
 \***************************************************************************/
 USHORT EXPENTRY DdeGetErrorString(err, cbMax, psz)
 USHORT err;
 USHORT cbMax;
 PSZ psz;
 {
     if (err < DMGERR_FIRST || err > DMGERR_LAST)
         return(0);
     return(WinLoadString(DMGHAB, hmodDmg, err, cbMax, psz));
 }



 /*\
 * HDATA stuff:
 *
 * Each thread has an hData list that contains all the hData's it has
 * been given by the DLL.   This list indicates what hData's an app can
 * and must eventually free.
 *
 * If an app has multiple threads registered, each threads pai's are linked
 * via the nextThread pointer.  The links are circular so the TID
 * should be used to know when all the lists are traversed.
 *
 * Each hData contains the following flags:
 *
 * HDATA_READONLY - set on any hData given to the DLL or created by the DLL.
 *         This prevents AddData from working.
 *
 * HDATA_APPOWNED - set at creation time by app so app will keep access
 *         until it frees it or unregistration happens.
 *         This prevents the DLL from freeing the hData before an app is
 *         through with it.
 *
 * HDATA_APPFREEABLE - set at creation time if logged into thread list.
 *
 * Each hData also contains the pai of the thread that created the hData.
 *         (set by PutData)  If APPOWNED is set, this identifies the
 *         owner thread as well.
 *
 *
 * General rules for apps:
 *
 *         hDatas from DLL calls are the apps responsibility to free and are
 *                 only valid till passed back into the DLL or freed.
 *         hDatas from callback calls are only valid during the callback.
 *         hDatas created by an app but not APPOWNED are only valid till
 *                 passed into the DLL.
 *         hDatas created by an app that are APPOWNED are valid until
 *                 the creating thread frees it or till that thread unregister
 *                 itself.
 *         A process will not loose access to an hData untill all threads that
 *                 have received the hData have freed it or passed it back to
 *                 the DLL (via callback)
 *
 *
 * Register:                                                       DONE
 *         creates hDataList for app/thread.
 *         creates nextThread links if applicable.
 *
 * DdePutData:                                                  DONE
 *         Only allows HDATA_APPOWNED flag
 *         sets HDATA_APPFREEABLE flag
 *         ...Falls into....
 * PutData:                                                        DONE
 *         Allocates selector
 *         sets creator pai
 *         sets HDATA_ flags specified
 *         if (HDATA_APPFREEABLE)
 *             adds to hDataList.
 *
 * DdeAddData:                                                  DONE
 *         fails if HDATA_READONLY or a non-dll type selector
 *
 * Callback:                                                       DONE
 *         Entry:
 *             for hData to callback:
 *                 Sets HDATA_READONLY if hData valid DLL type selector.
 *                 (does NOT add hData to thread list so he can't free it duri
 *                  the callback)
 *
 *         Exit:
 *             for hData to callback:
 *                 nothing.
 *             for hData from callback:
 *                 verifies creator == current
 *
 * DdeCreateInitPkt:                                            DONE
 * DdeAppNameServer:                                            NOT COMPLETE
 * DdeClientXfer:                                               DONE
 * DdeCheckQ:                                                   DONE
 *         if (valid selector)
 *             add to thread list
 *             if (valid DLL type selector)
 *                 Set READONLY flag.
 *
 * ProcessPkt:                                                     NOT IMP.
 *         for hData in:
 *              After used, calls FreeData()
 *         for hData out:
 *              before return, adds hData to thread list
 *
 * MyPostMsg:                                                      DONE
 *         gives if target process != current
 *         if target process is not a DLL process/thread, expand hszItem.
 *         calls FreeData() on current process if MDPM_FREEHDATA is set.
 *
 *
 * DdeFreeData:                                                 DONE
 *         if in thread list.
 *             remove hData from list
 *             if not in any thread lists for this process
 *                 free data
 *             return pass
 *         else
 *             return fail
 *
 * FreeData:                                                       DONE
 *         if (DLL type selector && HDATA_APPOWNED && creator == current)
 *             exit
 *         remove hData from thread list if found
 *         if not in any thread lists for this process
 *             free data
 *
 * UnRegister:                                                     DONE
 *         for each item in the hDataList:
 *             clear HDATA_APPOWNED flag if dll type selector and owned by thi
 *                 thread.
 *             FreeData()
 *         destroy list
 *         unlink from other thread lists
 *
 \*/


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HDMGDATA EXPENTRY DdePutData(pSrc, cb, cbOff, hszItem, usFmt, afCmd)
 * PBYTE pSrc;   // address of data to place into data handle or NULL
 * ULONG cb;     // amount of data to copy if pSrc is not NULL.
 * ULONG cbOff;  // offset into data handle region to place pSrc data
 * HSZ hszItem;  // item associated with this data
 * USHORT usFmt; // format associated with this data
 * USHORT afCmd; // HDATA_ flags.
 *
 * This api allows an application to create a hDmgData apropriate
 * for return from its call-back function.
 * The passed in data is stored into the hDmgData which is
 * returned on success.  Any portions of the data handle not filled are
 * undefined.  afCmd contains any of the HDATA_ constants described below:
 *
 * HDATA_APPOWNED
 *   This declares the created data handle to be the responsability of
 *   the application to free it.  Application owned data handles may
 *   be given to the DLL multiple times.  This allows a server app to be
 *   able to support many clients without having to recopy the data for
 *   each request.  If this flag is not specified, the data handle becomes
 *   invalid once passed to the DLL via any API or the callback function.
 *
 * NOTES:
 *   If an application expects this data handle to hold >64K of data via
 *   DdeAddData(), it should specify a cb + cbOff to be as large as
 *   the object is expected to get via DdeAddData() calls to avoid
 *   unnecessary data copying or reallocation by the DLL.
 *
 *   if psrc==NULL or cb == 0, no actual data copying takes place.
 *
 *   Data handles given to an application via the DdeClientXfer() or
 *   DdeCheckQueue() functions are the responsability of the client
 *   application to free and MUST NOT be returned from the callback
 *   function as server data!  The DLL will only accept data handles
 *   returned from the callback function that were created by the
 *   called application.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 HDMGDATA EXPENTRY DdePutData(pSrc, cb, cbOff, hszItem, usFmt, afCmd)
 PBYTE pSrc;
 ULONG cb;
 ULONG cbOff;
 HSZ hszItem;
 USHORT usFmt;
 USHORT afCmd;
 {
     PAPPINFO pai;

     if (!(pai = GetCurrentAppInfo(FALSE)))
         return(0L);;

     if (afCmd & ~(HDATA_APPOWNED)) {
         pai->LastError = DMGERR_INVALIDPARAMETER;
         return(0L);
     }

     return(PutData(pSrc, cb, cbOff, hszItem, usFmt, afCmd | HDATA_APPFREEABLE
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HDMGDATA EXPENTRY DdeAddData(hDmgData, pSrc, cb, cbOff)
 * HDMGDATA hDmgData;    // data handle to add data to
 * PBYTE pSrc;           // pointer to data to add (if NULL, no data copying)
 * ULONG cb;             // size of data to add
 * ULONG cbOff;          // offset within hData to place data
 *
 * This routine allows an application to add data to a hDmgData it had
 * previously created using DdePutData().  The handle will be
 * grown as necessary to support the data addition.  Data may be added
 * to a data handle multiple times and in any order of data locations.
 *
 * Once a data handle is given to the DLL via return from the callback
 * function or via a DLL API, it becomes readonly.  Further attempts to
 * add data to the hData by any process will fail.
 *
 * This call will return 0 if it failed to allocate enough memory or if
 * the data handle is readonly.  On success, the returned hDmgData should
 * replace the given hDmgData.  On failure, the hDmgData given is left
 * in the state it was initially.
 *
 * NOTE: For huge segments, or segments expected to grow to greater than
 *    64K, it is best if DdePutData() be called with a cbOff + cb as
 *    large as maximally expected so as not to force reallocation from a
 *    normal to a huge segment in 16bit applications.  This also avoids
 *    the need of replaceing the hDmgData with the output each time
 *    incase a new selector was needed to be allocated and copied to.
 *
 * PUBDOC END
 *
 * History:
 *   Created     9/17/89    Sanfords
 \***************************************************************************/
 HDMGDATA EXPENTRY DdeAddData(hDmgData, pSrc, cb, cbOff)
 HDMGDATA hDmgData;
 PBYTE pSrc;
 ULONG cb;
 ULONG cbOff;
 {
 #define pmyddes ((PMYDDES)hDmgData)
 #define selIn (SELECTOROF(hDmgData))

     PAPPINFO pai;
     SEL sel;
     ULONG cbOffAbs;

     if (!(pai = GetCurrentAppInfo(FALSE)))
         return(0L);;

     if (!CheckSel(selIn) ||
             pmyddes->offszItemName != sizeof(MYDDES) ||
             pmyddes->magic != MYDDESMAGIC ||
             pmyddes->fs & HDATA_READONLY) {
         pai->LastError = DMGERR_INVALID_HDMGDATA;
         return(0L);
     }

     cbOffAbs = pmyddes->offabData + cbOff;
     if (pmyddes->cbData + pmyddes->offabData < cb + cbOffAbs) {
         /*
          * need to grow...
          */
         if (cbOffAbs + cb > 0xFFFFL) {
             /*
              * going to be huge...
              */
             if ((pmyddes->offabData + pmyddes->cbData < 0xFFFFL) ||
                     DosReallocHuge(HIUSHORT(cb + cbOffAbs),
                     LOUSHORT(cb + cbOffAbs), selIn)) {
                 /*
                  * Either we can't grow a huge seg or we need to make one.
                  */
                 if (DosAllocHuge(HIUSHORT(cb + cbOffAbs),
                         LOUSHORT(cb + cbOffAbs), &sel, 0, SEG_GIVEABLE)) {
                     pai->LastError = DMGERR_MEMORY_ERROR;
                     return(0);
                 }
                 CopyHugeBlock(hDmgData, MAKEP(sel, 0), pmyddes->cbData +
                         sizeof(MYDDES) + 1);
                 FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&hDmgData,
                         FPI_DELETE);
                 DosFreeSeg(selIn);
                 hDmgData = MAKEP(sel, 0);
                 AddPileItem(pai->pHDataPile, (PBYTE)&hDmgData, NULL);
             }
         } else {
             /*
              * not going to be huge
              */
             if (DosReallocSeg((USHORT)(cb + cbOffAbs), selIn)) {
                 pai->LastError = DMGERR_MEMORY_ERROR;
                 return(0L);
             }
         }
         pmyddes->cbData = cbOff + cb;
     }
     if (pSrc)
         CopyHugeBlock(pSrc, HugeOffset((PBYTE)hDmgData, cbOffAbs), cb);
     return(hDmgData);
 #undef selIn
 #undef pmyddes
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * ULONG EXPENTRY DdeGetData(hDmgData, pDst, cbMax, cbOff)
 * HDMGDATA hDmgData;    // data handle to extract data from
 * PBYTE pDst;           // destination for extracted data
 * ULONG cbMax;          // destination buffer size
 * ULONG cbOff;          // offset into data to start extraction
 *
 * This copies up to cbMax bytes of data contained in the hDmgData data handle
 * at offset cbOff into application memory pointed to by pDst.
 * If pDst == NULL, no copying is performed.
 *
 * returns the size of the data contained in the data handle remaining after
 * cbOff or 0 if hDmgData is invalid or cbOff is too large.
 *
 * This API supports HUGE segments in 16 bit applications.
 *
 * PUBDOC END
 *
 * History:
 *   Created     12/14/88    Sanfords
 \***************************************************************************/
 ULONG EXPENTRY DdeGetData(hDmgData, pDst, cbMax, cbOff)
 HDMGDATA hDmgData;
 PBYTE pDst;
 ULONG cbMax;
 ULONG cbOff;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(0L);

     if (!CheckSel(SELECTOROF(hDmgData))) {
         pai->LastError = DMGERR_INVALID_HDMGDATA;
         return(0L);
     }
     if (cbOff >= ((PMYDDES)hDmgData)->cbData) {
         pai->LastError = DMGERR_INVALIDPARAMETER;
         return(0L);
     }
     cbMax = min(cbMax, ((PMYDDES)hDmgData)->cbData - cbOff);
     if (pDst == NULL)
         return(((PMYDDES)hDmgData)->cbData - cbOff);
     CopyHugeBlock(HugeOffset(DDES_PABDATA(hDmgData), cbOff), pDst, cbMax);
     return(cbMax);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * PBYTE EXPENTRY DdeAccessData(hDmgData)
 * HDMGDATA hDmgData;    // data handle to access
 *
 * This API returns a pointer to the data referenced by the data handle.
 * The pointer returned becomes invalid once the data handle is freed
 * or given to the DLL via callback return or API call.
 *
 * NOTE: applications MUST take care not to access beyond the limits of
 * the data handle.  Only hDmgData's created by the application via
 * a DdePutData() call may write to this memory prior to passing on
 * to any DLL API or returning from the callback function.  Any hDmgData
 * received from the DLL should be considered shared-readonly data and
 * should be treated as such.  This applies whether the application owns
 * the data handle or not.
 *
 * 0L is returned on error.
 *
 * PUBDOC END
 *
 * History:
 *   Created     8/24/88    Sanfords
 \***************************************************************************/
 PBYTE EXPENTRY DdeAccessData(hDmgData)
 HDMGDATA hDmgData;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(0L);
     if (CheckSel(SELECTOROF(hDmgData))) {
         return(DDES_PABDATA(hDmgData));
     }
     pai->LastError = DMGERR_ACCESS_DENIED;
     return(0L);
 }




 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeFreeData(hDmgData)
 * HDMGDATA hDmgData;        // data handle to destroy
 *
 * This routine should be called by an application wishing to release
 * custody of an hDmgData it owns.
 * An application owns any hDmgData
 * it created with the HDATA_APPOWNED flag or an hDmgData given to
 * it via DdeClientXfer(), DdeCheckQueue(), DdeAppNameServer(),
 * DdeCreateInitPkt() or DdeProcessPkt().
 *
 * Returns fSuccess.  This function will fail if the hDmgData is not
 * owned by the calling app.
 *
 * PUBDOC END
 * History:
 *   Created     12/14/88    Sanfords
 *   6/12/90 sanfords    Fixed to work with non-DLL generated selectors
 \***************************************************************************/
 BOOL EXPENTRY DdeFreeData(hDmgData)
 HDMGDATA hDmgData;
 {
     PAPPINFO pai;
     USHORT cbSel;
     TID tid;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(FALSE);

     cbSel = CheckSel(SELECTOROF(hDmgData));
     if (!cbSel) {
         pai->LastError = DMGERR_INVALID_HDMGDATA;
         return(FALSE);
     }
     SemEnter();
     /*
      * Apps can only free handles the DLL does not own or handles from extern
      * non-DLL DDE apps.
      */
     if (!FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&hDmgData, FPI_DELETE
         pai->LastError = DMGERR_INVALID_HDMGDATA;
         SemLeave();
         return(FALSE);
     }

     tid = pai->tid;
     do {
         if (FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&hDmgData, FPI_COU
             SemLeave();
             return(TRUE);
         }
         pai = pai->nextThread;
     } while (pai && pai->tid != tid);
     DosFreeSeg(SELECTOROF(hDmgData));

     SemLeave();
     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeCopyBlock(pSrc, pDst, cb)
 * PBYTE pSrc;   // source of copy
 * PBYTE pDst;   // destination of copy
 * ULONG cb;     // size in bytes of copy
 *
 * This copy utility can handle HUGE segments and can be used by any
 * application as a copy utility.  This does not support overlapping huge
 * copies.
 *
 * Returns fSuccess.
 *
 * PUBDOC END
 * History:      1/1/89  created         sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeCopyBlock(pSrc, pDst, cb)
 PBYTE pSrc;
 PBYTE pDst;
 ULONG cb;
 {
     return(CopyHugeBlock(pSrc, pDst, cb));
 }




 /***************************************************************************\
 * PUBDOC START
 * HSZ management notes:
 *
 *   HSZs are used in this DLL to simplify string handling for applications
 *   and for inter-process communication.  Since many applications use a
 *   fixed set of Application/Topic/Item names, it is convenient to convert
 *   them to HSZs and allow quick comparisons for lookups.  This also frees
 *   the DLL up from having to constantly provide string buffers for copying
 *   strings between itself and its clients.
 *
 *   HSZs are the same as atoms except they have no restrictions on length or
 *   number and are 32 bit values.  They are case preserving and can be
 *   compared directly for case sensitive comparisons or via DdeCmpHsz()
 *   for case insensitive comparisons.
 *
 *   When an application creates an HSZ via DdeGetHsz() or increments its
 *   count via DdeIncHszCount() it is essentially claiming the HSZ for
 *   its own use.  On the other hand, when an application is given an
 *   HSZ from the DLL via a callback, it is using another application's HSZ
 *   and should not free that HSZ via DdeFreeHsz().
 *
 *   The DLL insures that during the callback any HSZs given will remain
 *   valid for the duration of the callback.
 *
 *   If an application wishes to keep that HSZ to use for itself as a
 *   standard for future comparisons, it should increment its count so that,
 *   should the owning application free it, the HSZ will not become invalid.
 *   This also prevents an HSZ from changing its value.  (ie, app A frees it
 *   and then app B creates a new one that happens to use the same HSZ code,
 *   then app C, which had the HSZ stored all along (but forgot to increment
 *   its count) now is holding a handle to a different string.)
 *
 *   Applications may free HSZs they have created or incremented at any time
 *   by calling DdeFreeHsz().
 *
 *   The DLL internally increments HSZ counts while in use so that they will
 *   not be destroyed until both the DLL and all applications concerned are
 *   through with them.
 *
 *   IT IS THE APPLICATIONS RESPONSIBILITY TO PROPERLY CREATE AND FREE HSZs!!
 *
 * PUBDOC END
 \***************************************************************************/


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HSZ EXPENTRY DdeGetHsz(psz, country, codepage)
 * PSZ psz;          // string to HSZize.
 * USHORT country;   // country ID to use in comparisons.
 * USHORT codepage;  // codepage to use in comparisons.
 *
 * This routine returns a string handle to the psz passed in.
 * 0 is returned on failure or for NULL strings.
 *
 * If country is 0, the default system country code is used.
 * If codepage is 0, the default system codepage code is used.
 *
 * String handles are similar to atoms but without the 255
 * character limit on strings.  String handles are case preserving.
 * see DdeCmpHsz().
 *
 * String handles are consistant across all processes using the DLL.
 *
 * PUBDOC END
 *
 * History:      1/1/89 created         sanfords
 \***************************************************************************/
 HSZ EXPENTRY DdeGetHsz(psz, country, codepage)
 PSZ psz;
 USHORT country, codepage;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(0);

     return(GetHsz(psz, country, codepage, TRUE));
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeFreeHsz(hsz)
 * HSZ hsz;      // string handle to free
 *
 * This function decrements the usage count for the HSZ given and frees
 * it if the count becomes 0.
 *
 * PUBDOC END
 *
 * History:      1/1/89 created         sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeFreeHsz(hsz)
 HSZ hsz;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(0);
     return(FreeHsz(hsz));
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeIncHszCount(hsz)
 * HSZ hsz;  // string handle to increment.
 *
 * This function increments the usage count for the HSZ given.  This is
 * useful when an application wishes to keep an HSZ given to it in its
 * callback.
 *
 * PUBDOC END
 *
 * History:      1/1/89 created         sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeIncHszCount(hsz)
 HSZ hsz;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(FALSE)) == NULL)
         return(0);

     return(IncHszCount(hsz));
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * USHORT EXPENTRY DdeGetHszString(hsz, psz, cchMax)
 * HSZ hsz;      // string handle to extract string from
 * PSZ psz;      // buffer for case-sensitive string
 * ULONG cchMax; // buffer size.
 *
 * This API is the inverse of DdeGetHsz().  The actual length of the
 * string (without NULL terminator) referenced by hsz is returned.
 *
 * if psz is NULL, no string is returned in psz.
 * 0 is returned if hsz does not exist or is wild.
 * If hsz is wild, psz will be set to a 0 length string.
 *
 * PUBDOC END
 * History:      Created 5/10/89         sanfords
 \***************************************************************************/
 USHORT EXPENTRY DdeGetHszString(hsz, psz, cchMax)
 HSZ hsz;
 PSZ psz;
 ULONG cchMax;
 {
     if (psz) {
         if (hsz) {
             return(QueryHszName(hsz, psz, (USHORT)cchMax));
         } else {
             *psz = '\0';
             return(0);
         }
     } else if (hsz) {
         return(QueryHszLength(hsz));
     } else {
         return(0);
     }
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * SHORT EXPENTRY DdeCmpHsz(hsz1, hsz2)
 * HSZ hsz1, hsz2;   // string handles to compare.
 *
 * This routine returns:
 *   0  if hsz1 is of equal  rank to   hsz2
 *   -2 if hsz1 is invalid.
 *   -1 if hsz1 is of lower  rank than hsz2
 *   1  if hsz1 is of higher rank than hsz2.
 *   2  if hsz2 is invalid.
 *
 * Note that direct comparison of hszs (ie (hsz1 == hsz2)) is a case sensitive
 * comparison.  This function performs a case insensitive comparison.  Thus
 * different valued hszs may actually be equal.
 * A ranking is provided for binary searching.
 *
 * PUBDOC END
 \***************************************************************************/
 SHORT EXPENTRY DdeCmpHsz(hsz1, hsz2)
 HSZ hsz1, hsz2;
 {
     return(CmpHsz(hsz1, hsz2));
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * ULONG EXPENTRY DdeCheckQueue(hConv, phDmgData, idXfer, afCmd)
 * HCONV hConv;          // related convesation handle
 * PHDMGDATA phDmgData;  // OUTPUT: for resultant data handle, or NULL
 * ULONG idXfer;         // transaction ID or QID_NEWEST or QID_OLDEST
 * ULONG afCmd;          // queue operation code.
 *
 *       This routine checks a client conversation's assynchronous
 *       transaction queue for queued transaction status.  This allows a
 *       client to extract data or monitor the status of any transaction
 *       previously started asynchronously.  (TIMEOUT_ASSYNC) hConv is
 *       the client conversation who's queue is being checked.
 *
 *       If phDmgData is not NULL and the referenced item has data to
 *       return to the client, phDmgData is filled.  Returned hDmgDatas
 *       must be freed by the application.  phDmgData will be filled with
 *       0L if no return data is applicable or if the transaction is not
 *       complete.
 *
 *       If the queue is not periodicly flushed by an application
 *       issueing asynchronous transactions the queue is automaticly
 *       flushed as needed.  Oldest transactions are flushed first.
 *       DdeProcessPkt() and DdeDisconnect() and DdeEndEnumServers
 *       remove items from this queue.
 *
 *       idXfer is the transaction id returned by an asynchronous call to
 *       DdeClientXfer().
 *
 * afCmd = CQ_FLUSH - remove all items in the queue - return is fSuccess.
 * afCmd = CQ_REMOVE - the item referenced is removed from the queue.
 * afCmd = CQ_NEXT - references the idXfer AFTER (more recent than) the id
 *       given. 0 is returned if the ID given was the newest in the
 *       queue otherwise the next ID is returned.
 * afCmd = CQ_PREV - references the idXfer BEFORE (less recent than) the id
 *       given. 0 is returned if the ID given was the oldest in the
 *       queue otherwise the previous ID is returned.
 * afCmd = CQ_COUNT    - returns the number of entries in the queue.
 *
 *       By ORing in one of the following flags, the above flags can be
 *       made to reference the apropriate subset of queue entries:
 *
 * afCmd = CQ_ACTIVEONLY - incomplete active transactions only.
 * afCmd = CQ_COMPLETEDONLY - completed transactions only.
 * afCmd = CQ_FAILEDONLY - transactions which had protocol violations or
 *                         communication failures.
 * afCmd = CQ_INACTIVEONLY - The complement of CQ_ACTIVEONLY which is the
 *                           union of CQ_COMPLETEDONLY and CQ_FAILEDONLY.
 *
 * if phdmgdata = NULL, no hdmgdata is returned.
 * if idXfer == QID_NEWEST, the top-most (most recent) entry in the
 *       queue is referenced.
 * if idXfer == QID_OLDEST, the bottom-most (oldest) entry ID is
 *       referenced.
 *
 *       returns the ID of the item processed if it applies, or the count
 *       of items or fSuccess.
 *
 *
 * Standard usage examples:
 *
 *   To get the state of the oldest transaction:
 *   id = DdeCheckQueue(hConv, &hDmgData, QID_OLDEST, 0)
 *
 *   To get and flush the next completed transaction data, if there is
 *       any:
 *   id = DdeCheckQueue(hConv, &hDmgData, QID_OLDEST, CQ_REMOVE |
 *       CQ_INACTIVEONLY)
 *
 *   To flush all successfully completed transactions:
 *   DdeCheckQueue(hConv, NULL, QID_OLDEST, CQ_FLUSH | CQ_COMPLETEDONLY)
 *
 *   To see if a specific transaction is complete, and if so, to get the
 *   information and remove the transaction from the queue:
 *   if (DdeCheckQueue(hConv, &hDmgData, id, CQ_REMOVE | CQ_INACTIVEONLY))
 *       ProcessAndFreeData(hDmgData);
 *
 * PUBDOC END
 * History:      Created 6/6/89         sanfords
 \***************************************************************************/
 ULONG EXPENTRY DdeCheckQueue(hConv, phDmgData, idXfer, afCmd)
 HCONV hConv;
 PHDMGDATA phDmgData;
 ULONG idXfer;
 ULONG afCmd;
 {
     PAPPINFO pai;
     PCLIENTINFO pci;
     PCQDATA pcqd, pcqdT, pcqdEnd;
     USHORT err;
     ULONG retVal = TRUE;
     int i;

     if ((pai = GetCurrentAppInfo(TRUE)) == 0)
         return(0);

     SemCheckOut();
     SemEnter();

     err = DMGERR_INVALIDPARAMETER;
     if (!WinIsWindow(DMGHAB, hConv) ||
             !(BOOL)WinSendMsg(hConv, UM_QUERY, (MPARAM)Q_CLIENT, 0L) ||
             idXfer == QID_SYNC) {
         goto failExit;
     }

     if (!(pci = (PCLIENTINFO)WinSendMsg(hConv, UM_QUERY, (MPARAM)Q_ALL, 0L)))
         goto failExit;

     err = DMGERR_UNFOUND_QUEUE_ID;
     if ((pcqd = (PCQDATA)Findqi(pci->pQ, idXfer)) == NULL)
         goto failExit;

     /*
      * if referencing an end item, make sure it fits any subset flags.
      * If it doesn't we alter afCmd to force us to the first qualifying
      * entry in the correct direction.
      */
     if (!fInSubset(pcqd, afCmd)) {
         if (idXfer & (QID_OLDEST))
             afCmd |= CQ_NEXT;
         else if (idXfer & (QID_NEWEST))
             afCmd |= CQ_PREV;
         else if (!(afCmd & (CQ_NEXT | CQ_PREV | CQ_COUNT | CQ_FLUSH)))
             goto failExit;
     }

     if (afCmd & CQ_NEXT) {
         pcqdEnd = (PCQDATA)pci->pQ->pqiHead->next;
         if ((pcqd = (PCQDATA)pcqd->next) == pcqdEnd)
             goto failExit;
         while (!fInSubset(pcqd, afCmd)) {
             if ((pcqd = (PCQDATA)pcqd->next) == pcqdEnd)
                 goto failExit;
         }
     }

     if (afCmd & CQ_PREV) {
         pcqdEnd = (PCQDATA)pci->pQ->pqiHead;
         if ((pcqd = (PCQDATA)pcqd->prev) == pcqdEnd)
             goto failExit;
         while (!fInSubset(pcqd, afCmd)) {
             if ((pcqd = (PCQDATA)pcqd->prev) == pcqdEnd)
                 goto failExit;
         }
     }

     /*
      * pcqd now points to the apropriate entry
      */

     if (afCmd & CQ_COUNT)
         retVal = 0;
     else
         retVal = MAKEID(pcqd);

     /*
      * Fill phDmgData if specified.
      */
     if (phDmgData != NULL)
         if ((pcqd->xad.state == CONVST_CONNECTED) &&
                 CheckSel(SELECTOROF(pcqd->xad.pddes))) {
             *phDmgData = pcqd->xad.pddes;
             AddPileItem(pai->pHDataPile, (PBYTE)phDmgData, CmpULONG);
             if (((PMYDDES)*phDmgData)->magic == MYDDESMAGIC) {
                 ((PMYDDES)*phDmgData)->fs |= HDATA_READONLY;
             }
         } else
             *phDmgData = NULL;

     /*
      * remove pcqd if apropriate.
      */
     if (afCmd & (CQ_REMOVE | CQ_FLUSH)) {
         if (!FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&pcqd->xad.pddes,
                 0))
             FreeData((PMYDDES)pcqd->xad.pddes, pai);
         /*
          * Decrement the use count we incremented when the client started
          * this transaction.
          */
         FreeHsz(pcqd->XferInfo.hszItem);
         Deleteqi(pci->pQ, MAKEID(pcqd));
     }

     /*
      * go through entire list and flush or count if specified.
      */
     if (afCmd & (CQ_FLUSH | CQ_COUNT)) {
         pcqd = (PCQDATA)pci->pQ->pqiHead;
         for (i = pci->pQ->cItems; i; i--) {
             if (fInSubset(pcqd, afCmd)) {
                 if (afCmd & CQ_COUNT)
                     retVal++;
                 if (afCmd & CQ_FLUSH) {
                     pcqdT = (PCQDATA)pcqd->next;
                     /*
                      * Only free the data if not logged - ie the user never g
                      * copy.
                      */
                     if (!FindPileItem(pci->ci.pai->pHDataPile, CmpULONG,
                             (PBYTE)&pcqd->xad.pddes, 0))
                         FreeData((PMYDDES)pcqd->xad.pddes, pai);
                     /*
                      * Decrement the use count we incremented when the client
                      * this transaction.
                      */
                     FreeHsz(pcqd->XferInfo.hszItem);
                     Deleteqi(pci->pQ, MAKEID(pcqd));
                     pcqd = pcqdT;
                     continue;
                 }
             }
             pcqd = (PCQDATA)pcqd->next;
         }
     }

     SemLeave();
     SemCheckOut();
     return(retVal);

 failExit:
     pai->LastError = err;
     SemLeave();
     SemCheckOut();
     return(0);
 }



 BOOL fInSubset(pcqd, afCmd)
 PCQDATA pcqd;
 ULONG afCmd;
 {
     if (afCmd & CQ_ACTIVEONLY && (pcqd->xad.state <= CONVST_INIT1))
         return(FALSE);

     if (afCmd & CQ_INACTIVEONLY && (pcqd->xad.state > CONVST_INIT1))
         return(FALSE);

     if (afCmd & CQ_COMPLETEDONLY && (pcqd->xad.state != CONVST_CONNECTED))
         return(FALSE);

     if (afCmd & CQ_FAILEDONLY && (pcqd->xad.state > CONVST_TERMINATED))
         return(FALSE);
     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeEnableCallback(hConv, fEnable)
 * HCONV hConv;      // server conversation handle to enable/disable or NULL
 * BOOL fEnable;     // TRUE enables, FALSE disables callbacks.
 *
 * This routine is used by an application that does not want to be interrupted
 * by DLL calls to its Callback function.  When callbacks are disabled,
 * all non critical attempts to call the Callback function instead result in
 * the call being placed on a server transaction queue until callbacks are
 * reenabled.  An application should reenable callbacks in a timely manner
 * to avoid causing synchronous clients to time out.
 *
 * note that DdeProcessPkt() has the side effect of unblocking and removing
 * items from the server transaction queue when processing return packets.
 * DdeDisconnect() and DdeEndEnumServers() have the side effect of removing
 * any transactions for the disconnected conversation.
 *
 * If hConv is non-NULL, only the hConv conversation is affected.
 * If hConv is NULL, all conversations are affected.
 *
 * Callbacks can also be disabled on return from the callback function by
 * returning the constant CBR_BLOCK.  This has the same effect as
 * calling DdeEnableCallback(hConv, FALSE) except that the callback
 * returned from is placed back on the callback queue for later re-submission
 * to the callback function when the conversations callbacks are reenabled.
 * This allows a server that needs a long time to process a request to delay
 * returning the result without blocking within the callback function.
 *
 * No callbacks are made within this function.
 *
 * Note:  Callback transactions that have XTYPF_NOBLOCK set in their usType
 *       parameter cannot be blocked by CBR_BLOCK.
 *       These callbacks are issued to the server regardless of the state of
 *       callback enableing.
 *
 * fSuccess is returned.
 *
 * PUBDOC END
 * History:      Created 6/6/89         sanfords
 \***************************************************************************/
 BOOL EXPENTRY DdeEnableCallback(hConv, fEnable)
 HCONV hConv;
 BOOL fEnable;
 {
     PAPPINFO pai;

     if ((pai = GetCurrentAppInfo(TRUE)) == 0)
         return(FALSE);

     SemCheckOut();
     SemEnter();
     if (hConv == NULL) {
         pai->fEnableCB = fEnable = fEnable ? TRUE : FALSE;
         FlushLst(pai->plstCBExceptions);
     } else if (pai->fEnableCB != fEnable) {
         if (pai->plstCBExceptions == NULL)
             pai->plstCBExceptions = CreateLst(pai->hheapApp, sizeof(HWNDLI));
         AddHwndList((HWND)hConv, pai->plstCBExceptions);
     }
     SemLeave();
     if (fEnable)
         WinPostMsg(pai->hwndDmg, UM_CHECKCBQ, (MPARAM)pai, 0L);
     return(TRUE);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HDMGDATA EXPENTRY DdeAppNameServer(hszApp, afCmd);
 * HSZ hszApp;       // referenced app name
 * USHORT afCmd;     // action code.
 *
 * Updates or queries the DDE DLL application name server.  The DDE name
 * server acts as a filter for initiate messages on behalf of registered
 * applications and serves as a way for an application to determine what
 * other applications are available without the need for wild initiates.
 * Any change in the name server results in XTYP_REGISTER or XTYP_UNREGISTER
 * callbacks to all other applications.  Agents will not be allowed to
 * know about any other agent registrations.
 *
 * afCmd:
 *
 *   ANS_REGISTER
 *       Adds hszApp to the name server.  hszApp cannot be NULL.
 *       All other applications will receive a registration notification
 *       if their callback filters allow it.
 *       fSuccess is returned.
 *
 *   ANS_UNREGISTER
 *       Remove hszApp from the name server.  If hszApp==NULL the name server
 *       is cleared for the calling application.
 *       All other applications will receive a deregistration notification
 *       if their callback filters allow it.
 *       fSuccess is returned.
 *
 *   ANS_QUERYALLBUTME
 *       Returns a zero terminated array of hszApps/hApp pairs registered with
 *       the name server by all other applications.
 *       (Agent applications do not see other agent registered names.)
 *       If hszApp is set, only names matching* hszApp are placed into the lis
 *       0 is returned if no applicable names were found.
 *
 *   ANS_QUERYMINE
 *       Returns a zero terminated array of hszApps registered with the name
 *       server by the calling application.  If hszApp is set, only names
 *       matching hszApp are placed into the list.  0 is returned if no
 *       applicable names were found.
 *
 *   The following flags may be ORed in with one of the above flags:
 *
 *   ANS_FILTERON
 *       Turns on dde initiate callback filtering.  Only wild app names or
 *       those matching a registered name are given to the application for
 *       initiate requests or registration notifications.
 *
 *   ANS_FILTEROFF
 *       Turns off dde initiate callback filtering.  All initiate requests
 *       and registration notifications are passed onto the application.
 *
 * A 0 is returned on error.
 *
 * PUBDOC END
 *
 * History:
 \***************************************************************************/
 HDMGDATA EXPENTRY DdeAppNameServer(hszApp, afCmd)
 HSZ hszApp;
 USHORT afCmd;
 {
     PAPPINFO pai, paiT;
     BOOL fAgent;
     HDMGDATA hData;
     HSZ hsz;

     if ((pai = GetCurrentAppInfo(TRUE)) == 0)
         return(FALSE);

     if (afCmd & ANS_FILTERON)
         pai->afCmd |= DMGCMD_FILTERINITS;

     if (afCmd & ANS_FILTEROFF)
         pai->afCmd &= ~DMGCMD_FILTERINITS;


     if (afCmd & (ANS_REGISTER | ANS_UNREGISTER)) {

         if (pai->afCmd & DMGCMD_CLIENTONLY) {
             pai->LastError = DMGERR_DLL_USAGE;
             return(FALSE);
         }

         fAgent = pai->afCmd & DMGCMD_AGENT ? TRUE : FALSE;

         if (hszApp == NULL) {
             if (afCmd & ANS_REGISTER) {
                 /*
                  * registering NULL is not allowed!
                  */
                 pai->LastError = DMGERR_INVALIDPARAMETER;
                 return(FALSE);
             }
             /*
              * unregistering NULL is just like unregistering each
              * registered name.
              */
             while (PopPileSubitem(pai->pAppNamePile, (PBYTE)&hsz)) {
                 for (paiT = pAppInfoList; paiT; paiT = paiT->next) {
                     if (pai == paiT || (fAgent && (paiT->afCmd & DMGCMD_AGENT
                         continue;
                     WinPostMsg(paiT->hwndFrame, UM_UNREGISTER, (MPARAM)hsz,
                             (MPARAM)pai->hwndFrame);
                 }
             }
             return(TRUE);
         }

         if (afCmd & ANS_REGISTER) {
             if (pai->pAppNamePile == NULL)
                 pai->pAppNamePile = CreatePile(hheapDmg, sizeof(HSZ), 8);
             AddPileItem(pai->pAppNamePile, (PBYTE)&hszApp, NULL);
         } else
             FindPileItem(pai->pAppNamePile, CmpULONG, (PBYTE)&hszApp,
                     FPI_DELETE);

         for (paiT = pAppInfoList; paiT; paiT = paiT->next) {
             if (pai == paiT ||
                     (fAgent && (paiT->afCmd & DMGCMD_AGENT))) {
                 continue;
             }
             WinPostMsg(paiT->hwndFrame,
                     afCmd & ANS_REGISTER ? UM_REGISTER : UM_UNREGISTER,
                     (MPARAM)hszApp, (MPARAM)pai->hwndFrame);
         }
         return(TRUE);
     }

     if (afCmd & ANS_QUERYMINE) {
         hData = PutData(NULL, 10L * sizeof(HSZ),
                 0L, 0L, 0, HDATA_APPOWNED | HDATA_APPFREEABLE, pai);
         if (QueryAppNames(pai, hData, hszApp, 0L)) {
             return(hData);
         } else {
             DdeFreeData(hData);
             return(0L);
         }
     }

     if (afCmd & ANS_QUERYALLBUTME) {
         ULONG offAdd, offAddNew;

         hData = PutData(NULL, 10L * sizeof(HSZ),
                 0L, 0L, 0, HDATA_APPOWNED | HDATA_APPFREEABLE, pai);
         *((PHSZ)DDES_PABDATA((PDDESTRUCT)hData)) = 0L;
         SemEnter();
         paiT = pAppInfoList;
         offAdd = 0L;
         while (paiT) {
             if (paiT != pai &&
                     !(fAgent && (paiT->afCmd & DMGCMD_AGENT))) {
                 offAddNew = QueryAppNames(paiT, hData, hszApp, offAdd);
                 if (offAddNew == 0 && offAddNew < offAdd) {
                     /*
                      * memory error most likely.
                      */
                     SemLeave();
                     DdeFreeData(hData);
                     return(0L);
                 }
                 offAdd = offAddNew;
             }
             paiT = paiT->next;
         }
         SemLeave();
         return(hData);
     }

     return(0L);
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HDMGDATA EXPENTRY DdeCreateInitPkt(
 * HSZ hszApp,         // initiate app string
 * HSZ hszTopic,       // initiate topic string
 * HDMGDATA hDmgData)  // packet data containing language information.
 *
 * This routine is called by agent applications from within their callback
 * functions which need to store transaction initiate data into a
 * network-portable packet.  On return, HDMGDATA contains the packeted
 * data.  The calling application must free this data handle when
 * through.  Agents are given access to the app string so that they
 * can modify it if needed for their net protocol.
 *
 * PUBDOC END
 \***************************************************************************/
 HDMGDATA EXPENTRY DdeCreateInitPkt(hszApp, hszTopic, hDmgData)
 HSZ hszApp;
 HSZ hszTopic;
 HDMGDATA hDmgData;
 {
     PAPPINFO pai;

     hszApp;
     hszTopic;
     hDmgData;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);
     /*
      * Add hDataRet to thread list
      */
     pai->LastError = DMGERR_NOT_IMPLEMENTED;
 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * BOOL EXPENTRY DdeProcessPkt(
 * HDMGDATA hPkt,        // packet from net to process
 * ULONG hAgentFrom);    // foreign agent handle associated with this packet.
 *
 * This routine is called by agent applications which have received a packet
 * from another agent.  This call performs what actions are requested by the
 * hPkt.  If the particular transaction can be done synchronously, a return
 * packet is returned.  If not, an XTYP_RTNPKT callback will eventually be
 * sent to the agent and 0 is returned.  0 is also returned on error.
 *
 * hAgentFrom identifies the agent the packet came from.
 *
 * This call should NOT be made from within a callback.
 *
 * PUBDOC END
 \***************************************************************************/
 HDMGDATA EXPENTRY DdeProcessPkt(hPkt, hAgentFrom)
 HDMGDATA hPkt;
 ULONG hAgentFrom;
 {
     PAPPINFO pai;

     hPkt;
     hAgentFrom;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);
     pai->LastError = DMGERR_NOT_IMPLEMENTED;
 }



 /***************************** Public  Function ****************************\
 * PUBDOC START
 * ULONG EXPENTRY DdeQueryVersion()
 *
 * Returns the DLL version number: 0xjjmmuuppL
 * jj = major release;
 * mm = minor release;
 * uu = update number;
 * pp = platform ID; 1=OS/2, 2=DOS, 3=WINDOWS, 4=UNIX, 5=MAC, 6=SUN
 *
 *
 * PUBDOC END
 \***************************************************************************/
 ULONG EXPENTRY DdeQueryVersion()
 {
     return(MAKEULONG(MAKESHORT(1, rup),MAKESHORT(rmm, rmj)));
 }



 /* PUBDOC START
  * ----------------------- Platform specific APIs -----------------------
  * PUBDOC END
  */

 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HCONV EXPENTRY DdeConverseWithWindow(
 * HWND hwnd,            // server window to converse with
 * HSZ hszApp,           // app name to converse on
 * HSZ hszTopic,         // topic name to converse on
 * PCONVCONTEXT pCC)     // language information to converse on
 *
 * This creates a pre-initiated client conversation with the given hwnd.
 * The conversation is assumed to be on the given app, topic and context.
 * This is useful for implementing such things as drag and drop DDE
 * protocols.  hszApp and hszTopic cannot be NULL.  See
 * DdeCreateServerWindow().
 *
 * PUBDOC END
 \***************************************************************************/
 HCONV EXPENTRY DdeConverseWithWindow(hwnd, hszApp, hszTopic, pCC)
 HWND hwnd;
 HSZ hszApp, hszTopic;
 PCONVCONTEXT pCC;
 {
     PAPPINFO pai;

     UNUSED hwnd;
     UNUSED hszApp;
     UNUSED hszTopic;
     UNUSED pCC;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);

     if (QuerylatomLength((LATOM)hszApp) == 0 ||
             QuerylatomLength((LATOM)hszTopic) == 0) {
         pai->LastError = DMGERR_INVALIDPARAMETER;
         return(0);
     }

 }


 /***************************** Public  Function ****************************\
 * PUBDOC START
 * HWND DdeCreateServerWindow(hszApp, hszTopic, pCC)
 * HSZ hszApp,       // app name to accept conversations on.
 * HSZ hszTopic;     // topic name to accept conversations on.
 * PCONVCONTEXT pCC; // language information to accept or NULL for sys default
 *
 * This creates a pre_initiated server DDE window for the caller.  The
 * server window does not know what client window it will be talking to.  As
 * soon as any DDE message is received by the DDE server window, its
 * companion client hwnd is remembered and the conversation is 'locked in'.
 * The hwnd of the server window is returned.  hszApp and hszTopic cannot
 * be NULL.
 *
 * Typically, the server app would call CreateServerWindow() and pass the
 * hwnd to some prospective client via some other transport mechanism.  The
 * client would then call ConverseWithWindow() with the hwnd it received
 * from the server and then begin DDE transactions using the HCONV returned.
 *
 * A server may pass this hwnd to many potential clients.  Only the first
 * one that responds would get 'locked in'.  The rest would be out of luck
 * and their API calls would fail.  The server window ignores messages that
 * to not fit the app and topic given.
 *
 * PUBDOC END
 \****************************************************************************
 HWND EXPENTRY DdeCreateServerWindow(hszApp, hszTopic, pCC)
 HSZ hszApp, hszTopic;
 PCONVCONTEXT pCC;
 {
     PAPPINFO pai;
     INITINFO ii;
     HWND hwndServer;

     hszApp;
     hszTopic;
     pCC;

     if ((pai = GetCurrentAppInfo(TRUE)) == NULL)
         return(0);

     if (QuerylatomLength((LATOM)hszApp) == 0 ||
             QuerylatomLength((LATOM)hszTopic) == 0) {
         pai->LastError = DMGERR_INVALIDPARAMETER;
         return(0);
     }

     if ((hwndServer = CreateServerWindow(pai, hszTopic)) == NULL)
         return(0);

     ii.hszAppName = hszApp;
     ii.hszTopic = hszTopic;
     ii.hwndSend =
     ii.hwndFrame = NULL;
     ii.pCC = pCC;
     WinSendMsg(hwndServer, UMSR_INITIATE, 0L, 0L);
 }


 DDESPY.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DDESPY\DDESPY.C

 /***************************** Module Header ******************************\
 * Module Name: ddespy
 *
 * This is a small DDE ddespyr which lets me see whats going on.
 *
 * Created:      sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddespy.h"

 /*********** declares *********/

 HWND hwndExec = NULL;

 SWP gswp[IW_LAST - IW_FIRST + 1];
 PSZ apszTitles[] = {
         "Applications", "Topics", "System Items", "Data"
                    };


 PSZ apszType[] = {
                         "0-Type"        ,
                         "ACK"           ,
                         "ADVDATA"       ,
                         "ADVREQ"        ,
                         "ADVSTART"      ,
                         "ADVSTOP"       ,
                         "EXEC"          ,
                         "INIT"          ,
                         "INITCONF"      ,
                         "MONITOR"       ,
                         "PKT"           ,
                         "POKE"          ,
                         "REGISTER"      ,
                         "REQUEST"       ,
                         "RTNPKT"        ,
                         "TERM"          ,
                         "UNREGISTER"    ,
                         "WILDINIT"      ,
                         "XFERCOMP"      ,
                         "19"            ,
                         "20"            ,
                         "21"            ,
                         "22"            ,
                         "23"            ,
                         "24"            ,
                         "25"            ,
                         "26"            ,
                         "27"            ,
                         "28"            ,
                         "29"            ,
                         "30"            ,
                         "31"            ,
                };

 PSZ apszState[] = {         /* corresponds to convinfo.usStatus */
                         "DISCONNECTED"      ,
                         "CONNECTED"         ,
                         "NOADVISE"          ,
                         "ADVISE"            ,
                   };

 PSZ apszStatus[] = {        /* corresponds to convionfi.usConvst */
                         "NOACTIVITY"    ,
                         "INCOMPLETE"    ,
                         "TERMINATED"    ,
                         "CONNECTED"     ,
                         "INITIATING"    ,
                         "REQSENT"       ,
                         "DATARCVD"      ,
                         "POKESENT"      ,
                         "POKEACKRCVD"   ,
                         "EXECSENT"      ,
                         "EXECACKRCVD"   ,
                         "ADVSENT"       ,
                         "UNADVSENT"     ,
                         "ADVACKRCVD"    ,
                         "UNADVACKRCVD"  ,
                         "ADVDATASENT"   ,
                         "ADVDATAACKRCVD",
                    };


 #define GetLBCount(hw) (SHORT)WinSendMsg((hw), LM_QUERYITEMCOUNT, 0L, 0L)
 #define GetLBSelectedItem(hw) \
             (SHORT)WinSendMsg((hw), LM_QUERYSELECTION, (MPARAM)LIT_FIRST, 0L)
 #define GetLBItemHandle(hw, i) \
             (ULONG)WinSendMsg((hw), LM_QUERYITEMHANDLE, (MPARAM)(i), 0L)
 #define GetLBItemText(hw, i, cch, psz) \
             (ULONG)WinSendMsg((hw), LM_QUERYITEMTEXT, MPFROM2SHORT((i),(cch))
 #define DeleteLBItem(hw, i) WinSendMsg(hw, LM_DELETEITEM, MPFROMSHORT(i), 0L)
 #define NotifyOwner(hwnd, msg, mp1, mp2) \
             (WinSendMsg(WinQueryWindow(hwnd, QW_OWNER, FALSE), msg, mp1, mp2)


 void cdecl main(int argc, char **argv);
 void ResizeChildren(USHORT cxNew, USHORT cyNew);
 HDMGDATA EXPENTRY dataxfer(HCONV hConv, HSZ hszTopic, HSZ hszItem,
         USHORT usFmt, USHORT usType, HDMGDATA hDmgData);
 MRESULT EXPENTRY ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 MRESULT EXPENTRY GetTimeoutDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM
 MRESULT EXPENTRY EnhancedEFWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM
 MRESULT EXPENTRY ExecDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2);
 void Refresh(void);
 SHORT InsertLBItem(HWND hwndLB, PSZ psz, ULONG ulHandle);
 void SetLBEntries(HCONV hConv, HSZ hszItem, USHORT iwlb);
 void UpdateQueue(HWND hwndCB, HCONV hConv);
 void ConvInfoToString(PCONVINFO pci, PSZ psz, USHORT cbMax);
 PSZ mylstrcat(PSZ psz1, PSZ psz2, PSZ pszLast);
 int lstrlen(PSZ psz);

 /************* GLOBAL VARIABLES  ************/

 HAB hab;
 HMQ hmq;
 HHEAP hheap;
 HWND hwndFrame;
 HWND hwndClient;
 USHORT cyTitles;
 USHORT cxBorder;
 HCONVLIST hConvListMain;
 HSZ hszSysTopic;
 HSZ hszSysItemTopics;
 HSZ hszSysItemSysItems;
 ULONG gTimeout = 1000L;
 PFNWP lpfnSysEFWndProc;

 void cdecl main(argc, argv)
 int argc;
 char **argv;
 {
     USHORT err;
     QMSG qmsg;

     argc; argv;

     hab = WinInitialize(0);
     hmq = WinCreateMsgQueue(hab, 0);
     hheap = WinCreateHeap(0, 0, 0, 0, 0, HM_MOVEABLE);

     if (!WinRegisterClass(hab, "DDESpyr Class", ClientWndProc, CS_SIZEREDRAW,
         goto abort;

     cyTitles = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYTITLEBAR);
     cxBorder = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER);

     hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, NULL,
             (PSZ)"DDESpyr Class",
             (PSZ)"", WS_VISIBLE, (HMODULE)NULL, IDR_MAIN,
             &hwndClient);

     WinSetWindowText(hwndFrame, "DDE Spy");
     if (err = DdeInitialize((PFNCALLBACK)dataxfer, 0L, 0L)) {
         DdePostError(err);
         goto abort;
     }

     hszSysTopic = DdeGetHsz((PSZ)SZDDESYS_TOPIC, 0, 0);
     hszSysItemTopics = DdeGetHsz((PSZ)SZDDESYS_ITEM_TOPICS, 0, 0);
     hszSysItemSysItems = DdeGetHsz((PSZ)SZDDESYS_ITEM_SYSITEMS, 0, 0);

     Refresh();

     while (WinGetMsg(hab, &qmsg, 0, 0, 0)) {
         WinDispatchMsg(hab, &qmsg);
     }

     DdeUninitialize();
 abort:
     if (hwndFrame)
         WinDestroyWindow(hwndFrame);
     if (hheap) {
         WinDestroyHeap(hheap);
     }
     WinTerminate(hab);
     DosExit(TRUE, 0);
 }


 MRESULT EXPENTRY ClientWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1, mp2;
 {
     USHORT i;

     switch (msg) {
     case WM_CREATE:
         /*
          * initialize globals
          */
         hwndClient = hwnd;
         for (i = IW_APPSLBOX; i <= IW_ITEMSLBOX; i++) {
             gswp[i].hwnd = WinCreateWindow(hwndClient, WC_LISTBOX, "",
                     WS_VISIBLE | LS_NOADJUSTPOS, 0, 0, 0, 0, hwndClient,
                     HWND_TOP, i, NULL, NULL);
         }
         for (i = IW_APPSTITLE; i <= IW_DATATITLE; i++) {
             gswp[i].hwnd = WinCreateWindow(hwndClient, WC_STATIC,
                     apszTitles[i - IW_APPSTITLE],
                     WS_VISIBLE | SS_TEXT | DT_CENTER | DT_BOTTOM,
                     0, 0, 0, 0, hwndClient,
                     HWND_TOP, i, NULL, NULL);
         }
         gswp[IW_DATATEXT].hwnd = WinCreateWindow(hwndClient, WC_STATIC, "",
                 WS_VISIBLE | SS_TEXT | DT_WORDBREAK,
                 0, 0, 0, 0, hwndClient,
                 HWND_TOP, i, NULL, NULL);

         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDM_REFRESH:
             Refresh();
             break;

         case IDM_SETTIMEOUT:
             gTimeout = (USHORT)WinDlgBox(hwndFrame, NULL,
                     (PFNWP)GetTimeoutDlgProc,
                     (HMODULE)NULL, IDD_GETTIMEOUT, NULL);
             break;

         case IDM_EXEC:
             WinDlgBox(HWND_DESKTOP, hwnd, ExecDlgProc, NULL, IDD_EXEC, NULL);
             break;
         }
         break;

     case WM_CONTROL:
         switch (LOUSHORT(mp1)) {
         case IW_APPSLBOX:
             switch (HIUSHORT(mp1)) {
             case LN_SELECT:
                 hwnd = gswp[IW_APPSLBOX].hwnd;
                 SetLBEntries((HCONV)GetLBItemHandle(hwnd,
                         GetLBSelectedItem(hwnd)),
                         hszSysItemTopics, IW_TOPICSLBOX);
                 SetLBEntries((HCONV)GetLBItemHandle(hwnd,
                         GetLBSelectedItem(hwnd)),
                         hszSysItemSysItems, IW_ITEMSLBOX);
                 break;
             }
             break;

         case IW_TOPICSLBOX:
             break;

         case IW_ITEMSLBOX:
             break;

         default:
             goto DoDefAction;
         }
         break;

     case WM_SIZE:
         /*
          * resize children
          */
         ResizeChildren(SHORT1FROMMP(mp2), SHORT2FROMMP(mp2));
         break;

     case WM_ERASEBACKGROUND:
         return(TRUE);
         break;

     default:
 DoDefAction:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
     }
     return(0L);
 }



 void ResizeChildren(cxNew, cyNew)
 USHORT cxNew, cyNew;
 {
     USHORT i;

     for (i = IW_FIRST; i <= IW_LAST; i++) {
         gswp[i].fs = SWP_SIZE | SWP_MOVE | SWP_SHOW;
         gswp[i].hwndInsertBehind = HWND_TOP;
     }

     for (i = IW_APPSTITLE; i <= IW_ITEMSTITLE; i++) {
         gswp[i].x =
         gswp[i - IW_APPSTITLE + IW_APPSLBOX].x = i == IW_APPSTITLE ? -cxBorde
                 gswp[i - 1].x + gswp[i - 1].cx - cxBorder;

         gswp[i].y = cyNew - cyTitles;
         gswp[i - IW_APPSTITLE + IW_APPSLBOX].y = cyNew / 2;

         gswp[i].cx =
         gswp[i - IW_APPSTITLE + IW_APPSLBOX].cx = cxNew / 3;

         gswp[i].cy = cyTitles;
         gswp[i - IW_APPSTITLE + IW_APPSLBOX].cy = (cyNew / 2) - cyTitles;
     }

     gswp[IW_ITEMSLBOX].cx = cxNew - gswp[IW_ITEMSLBOX].x + cxBorder;

     gswp[IW_DATATITLE].cy = cyTitles;
     gswp[IW_DATATITLE].y =
     gswp[IW_DATATEXT].cy = cyNew / 2 - cyTitles;

     gswp[IW_DATATITLE].cx =
     gswp[IW_DATATEXT].cx = cxNew;

     WinSetMultWindowPos(hab, gswp, IW_DATATEXT - IW_APPSTITLE + 1);
 }


 void Refresh()
 {
     HCONV hConv;
     register NPSZ npszAppName;
     CONVINFO ci;
     USHORT cb;

     WinLockWindowUpdate(HWND_DESKTOP, gswp[IW_APPSLBOX].hwnd);
     WinSendMsg(gswp[IW_APPSLBOX].hwnd, LM_DELETEALL, 0L, 0L);
     WinSendMsg(gswp[IW_TOPICSLBOX].hwnd, LM_DELETEALL, 0L, 0L);
     WinSendMsg(gswp[IW_ITEMSLBOX].hwnd, LM_DELETEALL, 0L, 0L);

     hConvListMain = DdeBeginEnumServers(0L, hszSysTopic, hConvListMain, NULL,
     if (hConvListMain) {
         hConv = 0;
         while (hConv = DdeGetNextServer(hConvListMain, hConv)) {
             DdeQueryConvInfo(hConv, &ci, QID_SYNC);
             if (ci.hszAppPartner != 0) {
                 cb = DdeGetHszString(ci.hszAppPartner, NULL, 0L) + 1;
                 npszAppName = WinAllocMem(hheap, cb);
                 DdeGetHszString(ci.hszAppPartner, (PSZ)npszAppName, (ULONG)cb
                 InsertLBItem(gswp[IW_APPSLBOX].hwnd, (PSZ)npszAppName,
                         (ULONG)hConv);
                 WinFreeMem(hheap, npszAppName, cb);
             } else {
                 InsertLBItem(gswp[IW_APPSLBOX].hwnd, SZINDETERMINATE,
                         (ULONG)hConv);
             }
         }
     }
     WinLockWindowUpdate(HWND_DESKTOP, NULL);
 }


 SHORT InsertLBItem(hwndLB, psz, ulHandle)
 HWND hwndLB;
 PSZ psz;
 ULONG ulHandle;
 {
     SHORT ili;

     ili = (SHORT)WinSendMsg(hwndLB, LM_INSERTITEM, (MPARAM)LIT_SORTASCENDING,
             (MPARAM)psz);
     WinSendMsg(hwndLB, LM_SETITEMHANDLE, MPFROMSHORT(ili), (MPARAM)ulHandle);
     return(ili);
 }


 void SetLBEntries(hConv, hszItem, iwlb)
 HCONV hConv;
 HSZ hszItem;
 USHORT iwlb;
 {
     NPSZ npsz, npszT1, npszT2;
     BOOL fDone = 0;
     ULONG cb;
     HDMGDATA hDmgData;

     hDmgData = DdeClientXfer(0L, 0L, hConv, hszItem, DDEFMT_TEXT,
         XTYP_REQUEST, gTimeout, NULL);

     if (hDmgData == 0) {
         DdePostError(DdeGetLastError());
         return;
     }

     cb = DdeGetData(hDmgData, NULL, 0L, 0L);
     /*
      * BUG - may later want to handle the case for cb > 0xFFFF
      */
     npsz = WinAllocMem(hheap, (USHORT)cb);
     if (npsz == NULL) {
         DdePostError(DMGERR_MEMORY_ERROR);
         return;
     }
     if (DdeGetData(hDmgData, (PBYTE)npsz, cb, 0L) == 0) {
         DdePostError(DdeGetLastError());
         goto Exit;
     }
     npszT1 = npszT2 = npsz;
     WinLockWindowUpdate(HWND_DESKTOP, gswp[iwlb].hwnd);
     WinSendMsg(gswp[iwlb].hwnd, LM_DELETEALL, 0L, 0L);
     while (!fDone) {
         while (*npszT2 != '\t' && *npszT2 != '\0')
             npszT2++;
         if (*npszT2 == '\t') {
             *npszT2 = '\0';
             npszT2++;
         } else
             fDone = TRUE;
         InsertLBItem(gswp[iwlb].hwnd, (PSZ)npszT1,
                 (ULONG)DdeGetHsz(npszT1, 0, 0));
         npszT1 = npszT2;
     }
     WinLockWindowUpdate(HWND_DESKTOP, NULL);

 Exit:
     WinFreeMem(hheap, (NPBYTE)npsz, (USHORT)cb);
     return;
 }



 HDMGDATA EXPENTRY dataxfer(hConv, hszTopic, hszItem, usFmt, usType,
         hDmgData)
 HCONV hConv;
 HSZ hszTopic;
 HSZ hszItem;
 USHORT usFmt;
 USHORT usType;
 HDMGDATA hDmgData;
 {
     hConv; hszTopic; hszItem; usFmt; usType; hDmgData;

     switch (usType) {
     case XTYP_XFERCOMPLETE:
         if (hwndExec) {
             if (DdeCheckQueue(hConv, &hDmgData, (ULONG)hDmgData, 0L)) {
                 PSZ psz;
                 if (psz = (PSZ)DdeAccessData(hDmgData)) {
                     WinSetDlgItemText(hwndExec, IDEF_DATA, psz);
                     /*
                      * Free this data here.
                      */
                     DdeFreeData(hDmgData);
                 }
             }
         }
         break;
     }
     return(0);
 }


 MRESULT EXPENTRY GetTimeoutDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     USHORT usValue;

     switch (msg) {
     case WM_INITDLG:
         /*
          * set up our entryfield to be enhanced.
          */
         WinSetDlgItemShort(hwnd, IDC_EF, (USHORT)gTimeout, FALSE);
         lpfnSysEFWndProc = WinSubclassWindow(WinWindowFromID(hwnd, IDC_EF),
                 EnhancedEFWndProc);
         break;

     case ENHAN_ENTER:
         /*
          * when the user hits the enter key, it will be passed from the
          * entryfield to here and we will use it as a signal to exit.
          */
         WinQueryDlgItemShort(hwnd, IDC_EF, &usValue, FALSE);
         WinDismissDlg(hwnd, usValue);
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }

 MRESULT EXPENTRY EnhancedEFWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (msg) {
     case WM_CHAR:
         if (LOUSHORT(mp1) & KC_SCANCODE &&
                 LOUSHORT(mp1) & KC_KEYUP &&
                 /*---HACK ALERT!---*/
                 LOBYTE(LOUSHORT(mp2)) == 0x0d) {
             NotifyOwner(hwnd, ENHAN_ENTER,
                     (MPARAM)WinQueryWindowUShort(hwnd, QWS_ID), 0L);
         }
         break;
     }
     return(lpfnSysEFWndProc(hwnd, msg, mp1, mp2));
 }



 MRESULT EXPENTRY ExecDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     static UCHAR szT[255];
     static HCONV hConv = NULL;
     register USHORT i;
     USHORT cb;
     register USHORT xtyp;
     HDMGDATA hDmgData;
     HSZ hszApp, hszItem, hszTopic;
     BOOL fAssync;
     ULONG xid;

     switch (msg) {
     case WM_INITDLG:
         hwndExec = hwnd;
         WinSendDlgItemMsg(hwnd, IDEF_DATA, EM_SETTEXTLIMIT, (MPARAM)MAX_QSTRI
         WinSendDlgItemMsg(hwnd, IDCB_QDATA, EM_SETTEXTLIMIT, (MPARAM)MAX_QSTR
         if ((i = GetLBSelectedItem(gswp[IW_APPSLBOX].hwnd)) != LIT_NONE) {
             GetLBItemText(gswp[IW_APPSLBOX].hwnd, i, 255, szT);
             WinSetDlgItemText(hwnd, IDEF_APP, szT);
         }
         if ((i = GetLBSelectedItem(gswp[IW_TOPICSLBOX].hwnd)) != LIT_NONE) {
             GetLBItemText(gswp[IW_TOPICSLBOX].hwnd, i, 255, szT);
             WinSetDlgItemText(hwnd, IDEF_TOPIC, szT);
         }
         if ((i = GetLBSelectedItem(gswp[IW_ITEMSLBOX].hwnd)) != LIT_NONE) {
             GetLBItemText(gswp[IW_ITEMSLBOX].hwnd, i, 255, szT);
             WinSetDlgItemText(hwnd, IDEF_ITEM, szT);
         }
         WinSendDlgItemMsg(hwnd, IDRB_REQUEST, BM_CLICK, 0L, 0L);
         break;

     case WM_CONTROL:
     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case MBID_CANCEL:
             if (hConv != NULL)
                 DdeDisconnect(hConv);
             hConv = NULL;
             WinDismissDlg(hwnd, 0);
             break;

         case IDC_QFLUSH:
             DdeCheckQueue(hConv, NULL, QID_NEWEST, CQ_FLUSH);
             WinSetDlgItemText(hwnd, IDCB_QDATA, "");
             UpdateQueue(WinWindowFromID(hwnd, IDCB_QDATA), hConv);
             break;

         case IDC_QUPDATE:
             UpdateQueue(WinWindowFromID(hwnd, IDCB_QDATA), hConv);
             WinSendDlgItemMsg(hwnd, IDCB_QDATA, CBM_SHOWLIST, (MPARAM)TRUE, 0
             break;

         case IDC_DOIT:
             i = (USHORT)WinSendDlgItemMsg(hwnd, IDRB_ADVSTART,
                     BM_QUERYCHECKINDEX, 0L, 0L) + IDRB_ADVSTART;
             WinQueryDlgItemText(hwnd, IDEF_APP, 255, szT);
             hszApp = DdeGetHsz(szT, 0, 0);
             WinQueryDlgItemText(hwnd, IDEF_TOPIC, 255, szT);
             hszTopic = DdeGetHsz(szT, 0, 0);
             WinQueryDlgItemText(hwnd, IDEF_ITEM, 255, szT);
             hszItem = DdeGetHsz(szT, 0, 0);
             if (i != IDRB_REQUEST) {
                 WinQueryDlgItemText(hwnd, IDEF_DATA, 255, szT);
                 cb = WinQueryDlgItemTextLength(hwnd, IDEF_DATA);
             }
             if (hConv == NULL && !(hConv = DdeConnect(hszApp, hszTopic, NULL,
                 DdePostError(DdeGetLastError());
                 if (LOUSHORT(mp1) == MBID_OK)
                     WinDismissDlg(hwnd, 0);
                 return(0);
             }
             switch (i) {
             case IDRB_REQUEST:
                 xtyp = XTYP_REQUEST;
                 goto XferOut;
                 break;
             case IDRB_ADVSTART:
                 xtyp = XTYP_ADVSTART;
                 goto XferOut;
                 break;
             case IDRB_ADVSTOP:
                 xtyp = XTYP_ADVSTOP;
                 goto XferOut;
                 break;
             case IDRB_POKE:
                 xtyp = XTYP_POKE;
                 goto XferOut;
                 break;
             case IDRB_EXECUTE:
                 xtyp = XTYP_EXEC;
 XferOut:
                 fAssync = (BOOL)WinSendDlgItemMsg(hwnd, IDCBX_ASSYNC,
                         BM_QUERYCHECK, 0L, 0L);
                 if (!(hDmgData = DdeClientXfer((PBYTE)szT, (ULONG)cb + 1,
                         hConv, hszItem, DDEFMT_TEXT, xtyp,
                         fAssync ? TIMEOUT_ASYNC : gTimeout, &xid)))
                     DdePostError(DdeGetLastError());

                 if (fAssync) {
                     UpdateQueue(WinWindowFromID(hwnd, IDCB_QDATA), hConv);
                 } else {
                     if (i == IDRB_REQUEST) {
                         DdeGetData(hDmgData, szT, 255L, 0L);
                         DdeFreeData(hDmgData);
                         WinSetWindowText(gswp[IW_DATATEXT].hwnd, szT);
                         WinSetDlgItemText(hwnd, IDEF_DATA, szT);
                     }
                 }

             }
             break;
         }
         break;

     case WM_DESTROY:
         hwndExec = NULL;
     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }



 void UpdateQueue(hwndCB, hConv)
 HWND hwndCB;
 HCONV hConv;
 {
     SHORT lit;
     SHORT litSel = LIT_FIRST;
     CONVINFO ci;
     ULONG id, idSel;
     USHORT cItems;
     char szT[MAX_QSTRING];

     lit = (SHORT)WinSendMsg(hwndCB, LM_QUERYSELECTION,
             MPFROMSHORT(LIT_FIRST), 0L);
     idSel = (SHORT)WinSendMsg(hwndCB, LM_QUERYITEMHANDLE, MPFROMSHORT(lit), 0
     WinSendMsg(hwndCB, LM_DELETEALL, 0L, 0L);
     cItems = (USHORT)DdeCheckQueue(hConv, NULL, QID_NEWEST, CQ_COUNT);
     id = DdeCheckQueue(hConv, NULL, QID_NEWEST, 0L);
     while (cItems--) {
         DdeQueryConvInfo(hConv, &ci, id);
         ConvInfoToString(&ci, szT, MAX_QSTRING);
         lit = (SHORT)WinSendMsg(hwndCB, LM_INSERTITEM, MPFROMSHORT(LIT_END),
         WinSendMsg(hwndCB, LM_SETITEMHANDLE, MPFROMSHORT(lit), (MPARAM)id);
         if (id == idSel)
             litSel = lit;
         id = DdeCheckQueue(hConv, NULL, id, CQ_NEXT);
     }
     WinSendMsg(hwndCB, LM_SELECTITEM, MPFROMSHORT(litSel), (MPARAM)TRUE);
 }



 void ConvInfoToString(pci, psz, cbMax)
 PCONVINFO pci;
 PSZ psz;
 USHORT cbMax;
 {
     PSZ pszLast;
     char szT[100];

     pszLast = psz + cbMax - 1;
     *psz = '\0';
     psz = mylstrcat(psz, apszType[(pci->usType >> 4) && 0x1F], pszLast);
     psz = mylstrcat(psz, ": ", pszLast);
     psz = mylstrcat(psz, apszState[pci->usStatus & ST_CONNECTED ? 1 : 0], psz
     psz = mylstrcat(psz, " ", pszLast);
     psz = mylstrcat(psz, apszState[pci->usStatus & ST_ADVISE ? 3 : 2], pszLas
     psz = mylstrcat(psz, " ", pszLast);
     psz = mylstrcat(psz, apszStatus[pci->usConvst], pszLast);
     psz = mylstrcat(psz, " ", pszLast);
     if (pci->usFmt == DDEFMT_TEXT) {
         psz = mylstrcat(psz, "TEXT", pszLast);
     } else {
         DdeGetHszString(pci->hszItem, szT, 100L);
         psz = mylstrcat(psz, szT, pszLast);
     }
     psz = mylstrcat(psz, " ", pszLast);
     DdeGetHszString(pci->hszItem, szT, 100L);
     psz = mylstrcat(psz, szT, pszLast);

     if (pci->LastError) {
         psz = mylstrcat(psz, " ", pszLast);
         DdeGetErrorString(pci->LastError, pszLast - psz, psz);
     }
     pszLast = '\0';
 }


 /***************************** Public  Function ****************************\
 * Concatonates psz1 and psz2 into psz1.
 * returns psz pointing to end of concatonated string.
 * pszLast marks point at which copying must stop.  This makes this operation
 * safe for limited buffer sizes.
 *
 * History:  1/1/89  created sanfords
 \***************************************************************************/
 PSZ mylstrcat(psz1, psz2, pszLast)
 PSZ psz1, psz2, pszLast;
 {
     psz1 += lstrlen(psz1);
     while (*psz2 != '\0' && psz1 < pszLast) {
         *psz1++ = *psz2++;
     }
     *psz1 = '\0';
     return(psz1);
 }


 /***************************** Private Function ****************************\
 *
 * returns string length not counting null terminator.
 *
 * History:  1/1/89  created     sanfords
 \***************************************************************************/
 int lstrlen(psz)
 PSZ psz;
 {
     int c = 0;

     while (*psz != 0) {
         psz++;
         c++;
     }
     return(c);
 }



 DEMO.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DEMO\DEMO.C

 /****************************** Module Header ******************************\
 * Module Name:  demo.c - Demo application
 *
 * Created:
 *
 * Copyright (c) 1987  Microsoft Corporation
 *
 \***************************************************************************/

 #include "demo.h"
 #include <stdlib.h>

 /************* GLOBAL VARIABLES         */

 char szDemoClass[] = "Demo";

 HAB  hab;
 HMQ  hmqDemo;
 HWND hwndDemo;
 HWND hwndDemoFrame;
 HCONV hconv = NULL;
 HSZ hszTitle, hszTopicChase, hszItemPos;
 USHORT fmtSWP;
 SWP SWPTarget = { 0 };
 PFNWP RealFrameProc;
 BOOL flee = FALSE;
 USHORT cServers = 0;
 USHORT cxScreen, cyScreen;

 #define TIMEOUT 100
 #define TIMERSPEED 1000

 /**************************************/

 VOID CommandMsg(USHORT cmd)
 {
     UNUSED cmd;
 }

 BOOL DemoInit()
 {
     hab = WinInitialize(0);

     hmqDemo = WinCreateMsgQueue(hab, 0);

     cxScreen = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN);
     cyScreen = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);
     srand(2);

     if (!WinRegisterClass(hab, szDemoClass, (PFNWP)DemoWndProc,
             CS_SIZEREDRAW, 0))
          return(FALSE);

     /*
      * Initialize the ddeml
      */
     if (DdeInitialize((PFNCALLBACK)callback, 0L, 0L))
         return(FALSE);

     /*
      * Now create HSZs for each of our DDE strings.
      */
     fmtSWP = WinAddAtom(WinQuerySystemAtomTable(), "SWP FORMAT");
     hszTitle = DdeGetHsz("Demo", 0, 0);
     hszTopicChase = DdeGetHsz("Chaser", 0, 0);
     hszItemPos = DdeGetHsz("Position", 0, 0);

     /*
      * let others know we are here - available as a server and turn on
      * filtering so we don't get bothered with any initiates for any
      * other app names.
      */
     DdeAppNameServer(hszTitle, ANS_REGISTER | ANS_FILTERON);

     return(TRUE);
 }

 int cdecl main(int argc, char** argv)
 {
     ULONG fcf;
     QMSG qmsg;

     UNUSED argc;
     UNUSED argv;

     if (!DemoInit()) {
         WinAlarm(HWND_DESKTOP, WA_ERROR);
         return(0);
     }

     fcf = FCF_STANDARD;

     hwndDemoFrame = WinCreateStdWindow(
             HWND_DESKTOP,
             WS_VISIBLE,
             &fcf,
             szDemoClass,
             "",
             WS_VISIBLE,
             NULL,
             IDR_DEMO,
             &hwndDemo);

     WinSetFocus(HWND_DESKTOP, hwndDemo);

     while (WinGetMsg(hab, (PQMSG)&qmsg, NULL, 0, 0)) {
         WinDispatchMsg(hab, (PQMSG)&qmsg);
     }

     WinDestroyWindow(hwndDemoFrame);

     WinDestroyMsgQueue(hmqDemo);
     WinTerminate(hab);

     return(0);
 }

 /********** Demo Window Procedure **************/

 MRESULT FAR PASCAL DemoWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
     HPS   hps;
     RECTL rclPaint;
     SWP swp;
     SHORT speed;

     switch (msg) {
     case WM_CREATE:
         /* Set up this global first thing in case we need it elsewhere */
         hwndDemo = hwnd;
         hwndDemoFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         RealFrameProc = WinSubclassWindow(hwndDemoFrame, DemoFrameWndProc);
         WinSetWindowPos(hwndDemoFrame, NULL, 0, 0, 0, 0, SWP_MINIMIZE | SWP_S
         /*
          * start the timer so we will keep looking for another app like ours
          * to chase.
          */
         WinStartTimer(hab, hwndDemo, 1, TIMERSPEED);
         return(MRFROMSHORT(FALSE));
         break;

     case WM_TIMER:
         /*
          * We use a timer to keep us moving.
          */
         if (!hconv) {
             HDMGDATA hData;
             PHSZHAPP phszhapp;
             /*
              * no active conversation, try to make one.
              */
             WinStopTimer(hab, hwndDemo, 1);
             /*
              * find out if any others like us are out there
              */
             hData = DdeAppNameServer(hszTitle, ANS_QUERYALLBUTME);
             if (!hData) {
                 /*
                  * wait till others arrive.
                  */
                 return(0);
             }
             /*
              * extract the first hApp from the hData so we can connect to it
              */
             phszhapp = (PHSZHAPP)DdeAccessData(hData);
             if (phszhapp->hsz == 0) {
                 DdeFreeData(hData);
                 return(0);
             }
             /*
              * perform directed connection to our target
              */
             hconv = DdeConnect(hszTitle, hszTopicChase, NULL, phszhapp->hApp)
             /*
              * free the hData now that we are done using it.
              */
             DdeFreeData(hData);
             WinStartTimer(hab, hwndDemo, 1, TIMERSPEED);
             if (!hconv) {
                 /*
                  * cant make one, try again later.
                  */
                 return(0);
             }
             /*
              * Get the target's position into SWPTarget.
              */
             if (hData = DdeClientXfer(NULL, 0L, hconv, hszItemPos, fmtSWP,
                     XTYP_REQUEST, TIMEOUT, NULL)) {
                 DdeCopyBlock(DdeAccessData(hData), (PBYTE)&SWPTarget,
                         sizeof(SWP));
                 DdeFreeData(hData);
             }
             /*
              * set up an advise loop so our moving target keeps us informed
              * of where he is.
              */
             DdeClientXfer(NULL, 0L, hconv, hszItemPos, fmtSWP,
                     XTYP_ADVSTART, TIMEOUT, NULL);
         }

         if (WinIsWindow(hab, SWPTarget.hwnd)) {
             /*
              * target data must be valid, move toward it.
              */
             speed = 1;
             WinQueryWindowPos(hwndDemoFrame, &swp);
             if (swp.x > SWPTarget.x)
                 swp.x -= speed;
             if (swp.x < SWPTarget.x)
                 swp.x += speed;
             if (swp.y > SWPTarget.y)
                 swp.y -= speed;
             if (swp.y < SWPTarget.y)
                 swp.y += speed;
             swp.fs = SWP_MOVE | SWP_NOADJUST;
             WinSetMultWindowPos(hab, &swp, 1);
             if ((swp.x == SWPTarget.x) && (swp.y == SWPTarget.y) && (!flee))
                 /*
                  * he's cought stop chasing him and go find another.
                  */
                 WinAlarm(HWND_DESKTOP, WA_NOTE);
                 DdeDisconnect(hconv);
                 hconv = NULL;
                 /*
                  * move to a random position
                  */
                 WinSetWindowPos(hwndDemoFrame, HWND_TOP, rand() % cxScreen,
                         rand() % cyScreen, 0, 0,
                         SWP_MOVE | SWP_ZORDER | SWP_NOADJUST);
             }
         } else if (hconv) {
             /*
              * Target is invalid, disconnect and try a reconnect later.
              */
             DdeDisconnect(hconv);
             hconv = NULL;
         }
         break;

     case WM_PAINT:
         hps = WinBeginPaint(hwnd, (HPS)NULL, &rclPaint);
         DemoPaint(hwnd, hps, &rclPaint);
         WinEndPaint(hps);
         break;

     case WM_COMMAND:
         CommandMsg(LOUSHORT(mp1));
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }



 MRESULT FAR PASCAL DemoFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM
 {
     switch (msg) {
     case WM_MOVE:
         DdePostAdvise(hszTopicChase, hszItemPos);
         /* fall through */
     default:
         return(RealFrameProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }


 VOID DemoPaint(HWND hwnd, HPS hps, RECTL* prcl)
 {
     RECTL rcl;

     UNUSED prcl;

     /* get window interior rect */
     WinQueryWindowRect(hwnd, &rcl);

     /* print "Hello World" centered horizontally and vertically */
     WinDrawText(hps, -1, "Hello World", &rcl, SYSCLR_WINDOWTEXT,
             SYSCLR_WINDOW, DT_CENTER | DT_VCENTER | DT_ERASERECT);

     /* draw interior border */
     WinDrawBorder(hps, &rcl, 6, 6, SYSCLR_WINDOWTEXT, SYSCLR_WINDOW,
             DB_STANDARD);
 }



 HDMGDATA EXPENTRY callback(
 HCONV hConv,
 HSZ hszTopic,
 HSZ hszItem,
 USHORT usFmt,
 USHORT usType,
 HDMGDATA hDmgData)
 {
     SWP swp;

     UNUSED hConv;

     if (usType == XTYP_REGISTER && hszItem == hszTitle && !hconv) {
         /*
          * someone else came onboard, if we are looking for a target,
          * restart our clock.
          */
         WinStartTimer(hab, hwndDemo, 1, TIMERSPEED);
     }

     /*
      * we only care about stuff on our topic.
      */
     if (hszTopic != hszTopicChase)
         return(0);

     switch (usType) {

     case XTYP_ADVSTART:
         /*
          * Always allow advises on our item
          */
         return(hszItem == hszItemPos);
         break;

     case XTYP_ADVDATA:
         /*
          * Always accept advise data on our target's latest position.
          */
         if (hszItem == hszItemPos)
             DdeGetData(hDmgData, (PBYTE)&SWPTarget, sizeof(SWP), 0L);
         DdeFreeData(hDmgData);
         return(0);
         break;

     case XTYP_INIT:
         /*
          * always allow others to initiate with us on our topic.
          */
         return(hszItem == hszTitle && hszTopic == hszTopicChase);
         break;

     case XTYP_REQUEST:
     case XTYP_ADVREQ:
         /*
          * Respond to data requests as to our whereabouts item and format are
          * ok.
          */
         if (hszItem != hszItemPos || usFmt != fmtSWP)
             return(0);
         WinQueryWindowPos(hwndDemoFrame, &swp);
         return(DdePutData((PBYTE)&swp, sizeof(SWP), 0L, hszItemPos, fmtSWP, 0
         break;

     default:
         return(0);
     }
 }





 DIALOGS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLOCK\DIALOGS.C

 /*
     dialogs.c                Dialog procedures for PM Clock Application

     Created by Microsoft Corporation, 1989
 */
 #define INCL_WINDIALOGS
 #define INCL_WINBUTTONS
 #define INCL_WINSYS
 #include <os2def.h>
 #include <pmwin.h>
 #include "dialogs.h"

 /* defined in paint.c */
 extern USHORT usMajorTickPref ;
 extern USHORT usMinorTickPref ;
 extern LONG clrBackground ;
 extern LONG clrFace ;
 extern LONG clrHourHand ;
 extern LONG clrMinuteHand ;

 /* defined in clock.c */
 extern HWND hwndFrame ;

 /*
     ClkAboutDlgProc()                 "About..." dialog

     Returns:        MRESULT, 0 or return value from WinDefDlgProc
 */
 MRESULT EXPENTRY ClkAboutDlgProc ( HWND hwnd , USHORT usMsg ,
                                    MPARAM mp1 , MPARAM mp2 )
 {
     if ( usMsg == WM_COMMAND ) {
         WinDismissDlg ( hwnd , TRUE ) ;
         return 0L ;
     }
     else return WinDefDlgProc ( hwnd , usMsg , mp1 , mp2 ) ;
 }


 /*
     ClkTicksDlgProc()                "Ticks..." dialog

     Returns: MRESULT, 0 or return value from WinDefDlgProc
 */
 MRESULT EXPENTRY ClkTicksDlgProc ( HWND hwnd , USHORT usMsg ,
                                    MPARAM mp1 , MPARAM mp2 )
 {
     static USHORT usMajorTickSel ;
     static USHORT usMinorTickSel ;
     USHORT usButtonID ;

     switch ( usMsg ) {

         case WM_INITDLG :

             /* show the current major tick preference */
             WinSendMsg ( WinWindowFromID ( hwnd ,
                                            CLKTM_MAJOR | usMajorTickPref ) ,
                          BM_SETCHECK , MPFROM2SHORT ( TRUE , NULL ) , NULL )

             /* show the current minor tick preference */
             WinSendMsg ( WinWindowFromID ( hwnd ,
                                            CLKTM_MINOR | usMinorTickPref ) ,
                          BM_SETCHECK , MPFROM2SHORT ( TRUE , NULL ) , NULL )

             /* load the selection values from the preferences */
             usMajorTickSel = usMajorTickPref ;
             usMinorTickSel = usMinorTickPref ;

             /* let the default dialog procedure handle anything else */
             break ;

         case WM_COMMAND :

             switch ( LOUSHORT ( mp1 ) ) {

                 case DID_OK :

                     /* store away selections as preferences */
                     usMajorTickPref = usMajorTickSel ;
                     usMinorTickPref = usMinorTickSel ;

                     /* repaint with the new preferences */
                     WinInvalidateRect ( hwndFrame , NULL, TRUE ) ;

                 case DID_CANCEL :
                     WinDismissDlg ( hwnd , TRUE ) ;
             }

             return NULL ;

         case WM_CONTROL :

             if ( SHORT2FROMMP ( mp1 ) == BN_CLICKED ) {

                 usButtonID = SHORT1FROMMP ( mp1 ) ;

                 switch ( usButtonID & 0xff00 ) {

                     case CLKTM_MAJOR :
                         usMajorTickSel = LOBYTE ( usButtonID ) ;
                         break ;

                     case CLKTM_MINOR :
                         usMinorTickSel = LOBYTE ( usButtonID ) ;
                         break ;
                 }
             }

             /* fall through to the default control processing */
     }

     return WinDefDlgProc ( hwnd , usMsg , mp1 , mp2 ) ;
 }


 /*
     ClkColorsDlgProc()                "Clock Color Preferences" Dialog

     Returns: MRESULT, 0 or return value from WinDefDlgProc
 */
 MRESULT EXPENTRY ClkColorsDlgProc ( HWND hwnd , USHORT usMsg ,
                                    MPARAM mp1 , MPARAM mp2 )
 {
     USHORT usButtonID ;
     static USHORT usCheckedButtonID ;
     HWND hwndButton ;
     RECTL rclButton , rclButtonInterior ;
     static LONG clrBackgroundNew ;
     static LONG clrFaceNew ;
     static LONG clrHourHandNew ;
     static LONG clrMinuteHandNew ;
     static LONG * pclrNew ;

     switch ( usMsg ) {

         case WM_INITDLG :

             /* load the new values from the current ones */
             clrBackgroundNew = clrBackground ;
             clrFaceNew = clrFace ;
             clrHourHandNew = clrHourHand ;
             clrMinuteHandNew = clrMinuteHand ;

             /* click the "Background" radio button */
             WinSendMsg ( WinWindowFromID ( hwnd , CLKCLR_BACKGROUND ) ,
                          BM_CLICK , MPFROMSHORT ( TRUE ) , NULL ) ;

             /* let the default dialog procedure handle anything else */
             break ;

         case WM_COMMAND :

             switch ( LOUSHORT ( mp1 ) ) {
                 case DID_OK :

                     /* store the new values */
                     clrBackground = clrBackgroundNew ;
                     clrFace = clrFaceNew ;
                     clrHourHand = clrHourHandNew ;
                     clrMinuteHand = clrMinuteHandNew ;

                     /* repaint with the new colors */
                     WinInvalidateRect ( hwndFrame , NULL, TRUE ) ;

                 case DID_CANCEL :

                     WinDismissDlg ( hwnd , TRUE ) ;
             }
             return NULL ;

         case WM_CONTROL :

             usButtonID = SHORT1FROMMP ( mp1 ) ;

             /* selecting a new object */
             if ( usButtonID & CLKCLR_OBJECTS ) {

                 switch ( usButtonID ) {
                     case CLKCLR_BACKGROUND :
                         pclrNew = & clrBackgroundNew ;
                         break ;
                     case CLKCLR_FACE :
                         pclrNew = & clrFaceNew ;
                         break ;
                     case CLKCLR_HOURHAND :
                         pclrNew = & clrHourHandNew ;
                         break ;
                     case CLKCLR_MINUTEHAND :
                         pclrNew = & clrMinuteHandNew ;
                 }

                 /* click the button for the new object's current color */
                 WinSendMsg (
                     WinWindowFromID ( hwnd ,
                         CLKCLR_BUTTONSHIFT + ( USHORT ) * pclrNew ) ,
                     BM_CLICK , MPFROMSHORT ( TRUE ) , NULL ) ;

                 break ;
             }

             switch ( SHORT2FROMMP ( mp1 ) ) {

                 case BN_CLICKED :

                     * pclrNew = ( LONG ) usButtonID - CLKCLR_BUTTONSHIFT ;

                     /* turn off the check state of the previously checked
                      * button and turn on the check state of the button
                      * just clicked */

                     WinSendMsg ( WinWindowFromID ( hwnd , usCheckedButtonID )
                                  BM_SETCHECK , MPFROM2SHORT ( FALSE , NULL )
                                  NULL ) ;
                     WinSendMsg ( WinWindowFromID ( hwnd , usButtonID ) ,
                                  BM_SETCHECK , MPFROM2SHORT ( TRUE , NULL ) ,
                                  NULL ) ;

                     usCheckedButtonID = usButtonID ;

                     break ;

                 case BN_PAINT :

                     /* fill only the interior of the button, so we don't
                      * conflict with the focus indicator */

                     hwndButton = ( ( PUSERBUTTON ) mp2 ) -> hwnd ;
                     WinQueryWindowRect ( hwndButton , & rclButton ) ;
                     rclButton . xLeft ++ ;
                     rclButton . yBottom ++ ;
                     rclButton . xRight -- ;
                     rclButton . yTop -- ;
                     WinFillRect ( ( ( PUSERBUTTON ) mp2 ) -> hps ,
                                   & rclButton ,
                                   ( LONG ) usButtonID - CLKCLR_BUTTONSHIFT )

                     /* hollow out those buttons which aren't checked */
                     if ( ! WinSendMsg ( WinWindowFromID ( hwnd , usButtonID )
                                       BM_QUERYCHECK , NULL , NULL ) ) {
                         rclButtonInterior . xLeft = rclButton . xLeft + 4 ;
                         rclButtonInterior . yBottom = rclButton . yBottom + 4
                         rclButtonInterior . xRight = rclButton . xRight - 4 ;
                         rclButtonInterior . yTop = rclButton . yTop - 4 ;
                         WinFillRect ( ( ( PUSERBUTTON ) mp2 ) -> hps ,
                                       & rclButtonInterior , SYSCLR_WINDOW ) ;
                     }

                     break ;
             }

             /* fall through to the default control processing */
     }

     return WinDefDlgProc ( hwnd , usMsg , mp1 , mp2 ) ;
 }


 DLGPROC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\INIEDIT\DLGPROC.C

 /******************************* Module Header ******************************
 * Module Name: DlgProc.c
 *
 * Created by Microsoft Corporation, 1989
 *
 *
 * System Test Application
 *
 *
 \***************************************************************************/


 #define LINT_ARGS                           // Include needed parts of PM
 #define INCL_WININPUT                       //    definitions
 #define INCL_WINSYS
 #define INCL_WINMESSAGEMGR
 #define INCL_WINBUTTONS
 #define INCL_WINPOINTERS
 #define INCL_WINHEAP
 #define INCL_WINSHELLDATA
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINLISTBOXES
 #define INCL_WINENTRYFIELDS
 #define INCL_WINDIALOGS
 #define INCL_GPIBITMAPS
 #define INCL_GPIREGIONS
 #define INCL_GPILCIDS
 #define INCL_GPIPRIMITIVES
 #define INCL_DEV

 #include <string.h>
 #include <stdio.h>

 #include <os2.h>

 #include "IniEdit.h"


 /******************************* Constants *********************************/

 #define BUF_SIZE 132


 /******************************** Globals **********************************/

 static CHAR   szSearch[BUF_SIZE] = { 0 };           // Current search string
 static USHORT usLastIndex = 0;                      // Last Searched Item

 /******************************* Externals *********************************/

 extern USHORT        cAppNames;                     // see iniedit.c
 extern HWND          hwndList;
 extern PGROUPSTRUCT  pGroups;
 extern HAB           habIniEdit;
 extern HWND          FocusWindow;


 /****************************** Function Header ****************************\
 *
 * SearchWndProc
 *
 *
 * Handles the Search Dialog Box messages
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY SearchWndProc(HWND hwndDialog, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {
     HWND   hwndText;                            // Current Text Window


     switch (msg)
         {

         case WM_INITDLG:
             hwndText = WinWindowFromID( hwndDialog, IDDI_SEARCH_TEXT );
             WinSetWindowText(hwndText, szSearch);
             WinSendMsg( hwndText, EM_SETSEL,
                     MPFROM2SHORT(0, strlen(szSearch)), (MPARAM)0 );

             break;

         case WM_COMMAND:
             switch( LOUSHORT( mp1 ) )
                 {

                 case IDDI_SEARCH_OK:
                     hwndText = WinWindowFromID( hwndDialog, IDDI_SEARCH_TEXT
                     WinQueryWindowText( hwndText, BUF_SIZE, szSearch );
                     WinDismissDlg( hwndDialog, 0 );

                     if( (usLastIndex = SHORT1FROMMR(WinSendMsg( hwndList, LM_
                             MPFROM2SHORT( LSS_SUBSTRING, LIT_FIRST),
                             MPFROMP( szSearch )) ) != LIT_NONE ))
                         {
                         WinSendMsg( hwndList, LM_SELECTITEM,
                                 MPFROM2SHORT( (usLastIndex), NULL),
                                 MPFROM2SHORT( TRUE, NULL ) );
                         }
                     else  /* not found */
                         {
                         usLastIndex = LIT_FIRST;
                         WinAlarm( HWND_DESKTOP, 0);
                         }
                     break;

                 case IDDI_SEARCH_NEXT:
                     FindNext();
                     break;

                 default:
                     return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
                     break;
                 }

         default:
             return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* SearchWndProc */


 /****************************** Function Header ****************************\
 *
 * FindNext
 *
 *
 * Finds the next instance of the current search string starting from the
 * Last searched position
 *
 \***************************************************************************/

 VOID FindNext()
 {
    if( (usLastIndex = SHORT1FROMMR(WinSendMsg( hwndList, LM_SEARCHSTRING,
            MPFROM2SHORT( LSS_SUBSTRING, usLastIndex),
            MPFROMP( szSearch )) ) != LIT_NONE ))
        {
        WinSendMsg( hwndList, LM_SELECTITEM,
                MPFROM2SHORT( (usLastIndex), NULL),
                MPFROM2SHORT( TRUE, NULL ) );
        }
    else   /* alarm if not found */
        WinAlarm( HWND_DESKTOP, 0);

 }  /* FindNext */


 /****************************** Function Header ****************************\
 *
 * AddKeyWndProc
 *
 *
 * Handles the AddKey Dialog Box messages
 * Will facilitate adding new keys for a given App Name
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY AddKeyWndProc(HWND hwndDialog, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {
     HWND   hwndTextApp;                         // Handle for App Text Window
     HWND   hwndTextKey;
     HWND   hwndTextValue;
     CHAR   szApp[BUF_SIZE];                     // String Contents
     CHAR   szKey[BUF_SIZE];
     CHAR   szValue[BUF_SIZE];


     switch (msg)
         {
         case WM_INITDLG:
             WinSendDlgItemMsg(hwndDialog, IDDI_ADD_KEY_TEXT_APP, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             WinSendDlgItemMsg(hwndDialog, IDDI_ADD_KEY_TEXT_KEY, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             WinSendDlgItemMsg(hwndDialog, IDDI_ADD_KEY_TEXT_VAL, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             break;
         case WM_COMMAND:
             switch( LOUSHORT( mp1 ) )
                 {

                 case IDDI_ADD_KEY_OK:
                     hwndTextApp = WinWindowFromID( hwndDialog, IDDI_ADD_KEY_T
                     WinQueryWindowText( hwndTextApp, BUF_SIZE, szApp );

                     hwndTextKey = WinWindowFromID( hwndDialog, IDDI_ADD_KEY_T
                     WinQueryWindowText( hwndTextKey, BUF_SIZE, szKey );

                     hwndTextValue = WinWindowFromID( hwndDialog, IDDI_ADD_KEY
                     WinQueryWindowText( hwndTextValue, BUF_SIZE, szValue );

                     WinDismissDlg( hwndDialog, 0 );

                     /* if the App is NULL forget it */
                     if( *szApp == (CHAR)0 )
                         {
                         break;
                         }

                     /* if the Key is NULL forget it */
                     if( *szKey == (CHAR)0 )
                         {
                         break;
                         }

                     /* if the Value is NULL forget it */
                     if( *szValue == (CHAR)0 )
                         {
                         break;
                         }

                     if( !WinWriteProfileString( habIniEdit, szApp, szKey, szV
                         ;
                     break;

                 default:
                     return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
                     break;
                 }

         default:
             return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* AddKeyWndProc */


 /****************************** Function Header ****************************\
 *
 * ChangeKeyWndProc
 *
 *
 * Handles the ChangeKey Dialog Box messages
 * Will facilitate changing a key's value given an app, key and new value
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY ChangeKeyWndProc(HWND hwndDialog, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {
     HWND     hwndTextApp;                       // Handle for App Text Window
     HWND     hwndTextKey;
     HWND     hwndTextVal;
     CHAR     szApp[BUF_SIZE];                   // String Contents
     CHAR     szKey[BUF_SIZE];
     CHAR     szVal[BUF_SIZE];


     switch (msg)
         {
         case WM_INITDLG:
             if( FocusWindow )
                 {

                 FocusWindow = WinWindowFromID( hwndDialog, IDDI_CHANGE_KEY_TE
                 WinSetFocus( HWND_DESKTOP, FocusWindow);
                 WinQueryWindowText( FocusWindow, BUF_SIZE, szVal );

                 FocusWindow = (HWND)NULL;

                 return((MRESULT) TRUE );
                 } else {
                 WinSendDlgItemMsg(hwndDialog, IDDI_CHANGE_KEY_TEXT_APP, EM_SE
                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                 WinSendDlgItemMsg(hwndDialog, IDDI_CHANGE_KEY_TEXT_KEY, EM_SE
                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                 WinSendDlgItemMsg(hwndDialog, IDDI_CHANGE_KEY_TEXT_VAL, EM_SE
                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                 }
             break;

         case WM_COMMAND:
             switch( LOUSHORT( mp1 ) )
                 {

                 case IDDI_CHANGE_KEY_OK:
                     hwndTextApp = WinWindowFromID( hwndDialog, IDDI_CHANGE_KE
                     WinQueryWindowText( hwndTextApp, BUF_SIZE, szApp );

                     hwndTextKey = WinWindowFromID( hwndDialog, IDDI_CHANGE_KE
                     WinQueryWindowText( hwndTextKey, BUF_SIZE, szKey );

                     hwndTextVal = WinWindowFromID( hwndDialog, IDDI_CHANGE_KE
                     WinQueryWindowText( hwndTextVal, BUF_SIZE, szVal );


                     WinDismissDlg( hwndDialog, IDDI_CHANGE_KEY_OK );

                     /* if the App is NULL forget it */
                     if( *szApp == (CHAR)0 )
                         {
                         break;
                         }

                     /* if the Key is NULL forget it */
                     if( *szKey == (CHAR)0 )
                         {
                         break;
                         }

                     /* if the Value is NULL forget it */
                     if( *szVal == (CHAR)0 )
                         {
                         break;
                         }


                     if( !WinWriteProfileString( habIniEdit, szApp, szKey, szV

                     break;

                 default:
                     return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
                     break;
                 }

         default:
             return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* ChangeKeyWndProc */


 /****************************** Function Header ****************************\
 *
 * DelKeyWndProc
 *
 *
 * Handles the DelKey Dialog Box messages
 * Will facilitate deleting a key value given an app and the key
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY DelKeyWndProc(HWND hwndDialog, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {
     HWND   hwndTextApp;                         // Handle for App Text Window
     HWND   hwndTextKey;
     CHAR   szApp[BUF_SIZE];                     // String Contents
     CHAR   szKey[BUF_SIZE];


     switch (msg)
         {
         case WM_INITDLG:
             WinSendDlgItemMsg(hwndDialog, IDDI_DEL_KEY_TEXT_APP, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             WinSendDlgItemMsg(hwndDialog, IDDI_DEL_KEY_TEXT_KEY, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             break;
         case WM_COMMAND:
             switch( LOUSHORT( mp1 ) )
                 {

                 case IDDI_DEL_KEY_OK:
                     hwndTextApp = WinWindowFromID( hwndDialog, IDDI_DEL_KEY_T
                     WinQueryWindowText( hwndTextApp, BUF_SIZE, szApp );

                     hwndTextKey = WinWindowFromID( hwndDialog, IDDI_DEL_KEY_T
                     WinQueryWindowText( hwndTextKey, BUF_SIZE, szKey );


                     WinDismissDlg( hwndDialog, 0 );

                     /* if the App is NULL forget it */
                     if( *szApp == (CHAR)0 )
                         {
                         break;
                         }

                     /* if the Key is NULL forget it */
                     if( *szKey == (CHAR)0 )
                         {
                         break;
                         }


                     if( !WinWriteProfileString( habIniEdit, szApp, szKey, (PC
                         ;
                     break;

                 default:
                     return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
                     break;
                 }

         default:
             return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* DelKeyProc */


 /****************************** Function Header ****************************\
 *
 * DelAppWndProc
 *
 *
 * Handles the DelApp Dialog Box messages
 * Will facilitate deleting all keys from a given app name
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY DelAppWndProc(HWND hwndDialog, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {
     HWND   hwndTextApp;                         // App Name Window
     CHAR   szApp[BUF_SIZE];                     // String Contents of Window


     switch (msg)
         {
         case WM_INITDLG:
             WinSendDlgItemMsg(hwndDialog, IDDI_DEL_APP_TEXT_APP, EM_SETTEXTLI
                     MPFROMSHORT(MAX_STRING_LEN), 0L);
             break;

         case WM_COMMAND:
             switch( LOUSHORT( mp1 ) )
                 {

                 case IDDI_DEL_APP_OK:
                     hwndTextApp = WinWindowFromID( hwndDialog, IDDI_DEL_APP_T
                     WinQueryWindowText( hwndTextApp, BUF_SIZE, szApp );

                     WinDismissDlg( hwndDialog, 0 );

                     /* if the App is NULL forget it */
                     if( *szApp == (CHAR)0 )
                         {
                         break;
                         }

                     if( !WinWriteProfileString( habIniEdit, szApp, (PCHAR)NUL
                         ;

                     break;

                 default:
                     return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
                     break;
                 }

         default:
             return WinDefDlgProc(hwndDialog, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* DelAppWndProc */


 DLGSAMP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DLGSAMP\DLGSAMP.C

 /*
     DLGSAMP -- Dialog Box Sample Application
     Created by Microsoft Corporation, 1989
 */

 #define INCL_WINBUTTONS
 #define INCL_WINDIALOGS
 #define INCL_WINERRORS
 #define INCL_WINFRAMEMGR
 #define INCL_WININPUT
 #define INCL_WINLISTBOXES
 #define INCL_WINMENUS
 #define INCL_WINMESSAGEMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINSWITCHLIST
 #define INCL_WINSYS
 #define INCL_WINWINDOWMGR
 #define M_I86L
 #include <os2.h>

 #include <string.h>
 #include <stdlib.h>

 #include "dlgsamp.h"
 #include "dlgsamp1.h"

 /*
     Function Prototypes
 */

 VOID NEAR cdecl main(VOID);

 /* Local Routines */
 VOID cdecl CenterDlgBox(HWND);
 VOID cdecl CheckColor(HWND, SHORT, COLOR *);
 VOID cdecl EnableModality(HWND, BOOL);
 BOOL cdecl IsIntInRange(HWND, SHORT, SHORT, SHORT, SHORT, SHORT);
 VOID cdecl LoadDialog(HWND, HWND, SHORT, PFNWP, BOOL);
 VOID cdecl MainWndCommand(HWND, USHORT, BOOL *);
 VOID cdecl MainWndPaint(HWND);
 VOID cdecl SetModality(HWND, BOOL);
 VOID cdecl Trace(PSZ, PSZ);

 /* Window Procedures */
 MRESULT EXPENTRY fnwpMainWnd(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY fnwpEntryFieldDlg(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY fnwpAutoRadioButtonDlg(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY fnwpCheckBoxDlg(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY fnwpListBoxDlg(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY fnwpAboutBoxDlg(HWND, USHORT, MPARAM, MPARAM);

 /*
     Global variables
 */
 COLOR colorClient = CLR_RED | CLR_BLUE;    /* Color of client area    */

 CHAR  szEntryField1[10] = "";              /* Used to pass back info  */
 CHAR  szEntryField2[10] = "";              /* from entry fields       */
                                            /* in EntryFieldDlg        */

 BOOL  bModality = TRUE;                    /* Does the user want modal*/
                                            /* or modeless dialogs?    */
 COLOR colorSave;
 CHAR  szSelection[LEN_LISTBOXENTRY] = "";  /* Used to pass back       */
                                            /* list box item selected  */
                                            /* in ListBoxDlg           */

 HAB   hab;                                 /* Anchor block handle     */
 HWND  hwndClient;                          /* Client Window handle    */
 HWND  hwndFrame;                           /* Frame Window handle     */
 HWND  hwndModelessDlg;                     /* Modeless Dialog handle  */

 /**************************************************************************
 *
 *   FUNCTION: main
 *
 *   Typical PM main function which initializes PM, creates a message queue,
 *   registers a window class, creates a window, gets and dispatches
 *   messages to its winproc until its time to quit, and then tidies up
 *   before terminating.
 *
 **************************************************************************/

 VOID NEAR cdecl main(  )
 {
   HMQ     hmq;                        /* Message Queue handle         */
   QMSG    qmsg;                       /* Message                      */
   ULONG   flCreate;

   hab   = WinInitialize( 0 );         /* Initialize PM                */
   hmq   = WinCreateMsgQueue( hab, 0 );/* Create application msg queue */

   WinRegisterClass(                   /* Register Window Class        */
       hab,                            /* Anchor block handle          */
       "DlgSamp Class",                /* Window Class name            */
       fnwpMainWnd,                    /* Address of Window Procedure  */
       (ULONG) NULL,                   /* No special class style       */
       0                               /* No extra window words        */
       );

   flCreate = FCF_STANDARD & ~FCF_ACCELTABLE;

   hwndFrame = WinCreateStdWindow(
         HWND_DESKTOP,                   /* Desktop Window is parent     */
         WS_VISIBLE,                     /* Window styles                */
         (PVOID)&flCreate,               /* Window creation parameters   */
         "DlgSamp Class",                /* Window Class name            */
         "",                             /* Window Text                  */
         0L,                             /* Client style                 */
         (HMODULE) NULL,                 /* Module handle                */
         ID_MAINWND,                     /* Window ID                    */
         (HWND FAR *)&hwndClient         /* Client Window handle         */
         );


   /* Message Loop */
   while( WinGetMsg( hab, (PQMSG)&qmsg, (HWND)NULL, 0, 0 ) )
     WinDispatchMsg( hab, (PQMSG)&qmsg );

   /* Cleanup code */
   WinDestroyWindow( hwndFrame );
   WinDestroyMsgQueue( hmq );
   WinTerminate( hab );
 }

 /***********************************************************************
 *
 *   WinProc: fnwpMainWnd
 *
 *   Controls the state of the menu, and loads various dialogs.  The
 *   dialogs will be modal or modeless depending on the setting of the
 *   Modality menuitem.
 *
 ***********************************************************************/

 MRESULT  EXPENTRY fnwpMainWnd( hwnd, message, mp1, mp2 )
 HWND    hwnd;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
   USHORT   Command;   /* Command passed by WM_COMMAND */
   SHORT    id;        /* ID of item selected from the list box */

   switch(message)
   {
     case WM_PAINT:
       MainWndPaint( hwnd );  /* Invoke window painting routine */
       break;
     case WM_HELP:
 /*********************************************************************
 *
 *   This will be received when either:-
 *
 *   1. The user hits the F1 key
 *   2. The user clicks on the action bar item F1=Help
 *
 *********************************************************************/
       WinMessageBox( HWND_DESKTOP,
                      hwndFrame,
                      (PSZ)"Dialog Sample Application: Help",
                      (PSZ)"Try out the pulldown menus, or Alt+selection",
                      ID_MB,
                      MB_OK );
       break;
     case WM_COMMAND:
       Command = SHORT1FROMMP( mp1 );
       MainWndCommand( hwnd, Command, &bModality );
       break;
     case DLGSAMP_EFCOMPLETE:
       WinQueryWindowText( WinWindowFromID( hwndModelessDlg, EF_1 ),
                           sizeof( szEntryField1 ), szEntryField1 );
       WinQueryWindowText( WinWindowFromID( hwndModelessDlg, EF_2 ),
                           sizeof( szEntryField2 ), szEntryField2 );
       WinInvalidateRect( hwnd, NULL, FALSE );/* Request whole window repaint
       break;
     case DLGSAMP_LBCOMPLETE:
       id = SHORT1FROMMR( WinSendDlgItemMsg( hwndModelessDlg,
                                             LB_1,
                                             LM_QUERYSELECTION,
                                             0L,
                                             0L ) );
       if( id == LIT_NONE )
         strcpy( szSelection, "" );
       else
         WinSendDlgItemMsg( hwndModelessDlg,
                            LB_1,
                            LM_QUERYITEMTEXT,
                            MPFROM2SHORT( id, sizeof( szSelection ) ),
                            MPFROMP( szSelection ) );
       break;
     case DLGSAMP_RBCOMPLETE:
     case DLGSAMP_CBCOMPLETE:
       break;
     case DLGSAMP_DESTROYDLG:
       WinDestroyWindow( hwndModelessDlg );
       EnableModality( hwndFrame, TRUE );
       WinInvalidateRect( hwnd, NULL, FALSE );/* Request whole window repaint
       break;
     case WM_CLOSE:
       WinPostMsg( hwnd, WM_QUIT, 0L, 0L );  /* Cause termination    */
       break;
     default:
       return WinDefWindowProc( hwnd, message, mp1, mp2 );
   }
   return FALSE;
 }

 /***********************************************************************
 *
 *  DlgProc:  fnwpEntryFieldDlg
 *
 *  A dialog proc which captures and validates the contents of two
 *  entry fields.
 *
 ***********************************************************************/

 MRESULT EXPENTRY fnwpEntryFieldDlg( hwndDlg, message, mp1, mp2 )
 HWND    hwndDlg;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
   switch (message)
   {
     case WM_INITDLG:
       CenterDlgBox( hwndDlg );
       break;
     case WM_COMMAND:
       switch( SHORT1FROMMP( mp1 ) )
       {
         case DID_OK: /* Enter key or pushbutton pressed/ selected */

 /***************************************************************************
 *
 *   Validate the contents of the two entry fields
 *
 ***************************************************************************/

           if( !IsIntInRange( hwndDlg, EF_1, 1, 100, ERR_EFINVALID, ID_MB ) )
             return FALSE;
           if( !IsIntInRange( hwndDlg, EF_2, 1, 100, ERR_EFINVALID, ID_MB ) )
             return FALSE;

 /***************************************************************************
 *
 *   A modal dialog is destroyed before control is returned to the
 *   invoking winproc, so it must pass the contents of its Entry Fields etc.
 *   back to the invoking window before it returns.
 *
 *   When a modeless dialog box returns it still continues to exist. It
 *   could pass the contents of its Entry Fields etc. back to the
 *   invoking window in several ways.
 *
 *   Here a user message is posted to the invoking window to say that the
 *   dialog has completed. The invoking window then has an opportunity
 *   to extract the contents of the Entry Fields etc.
 *
 ***************************************************************************/

           if( bModality )
           {
             WinQueryWindowText( WinWindowFromID( hwndDlg, EF_1 ),
                                 sizeof( szEntryField1),
                                 szEntryField1 );
             WinQueryWindowText( WinWindowFromID( hwndDlg, EF_2 ),
                                 sizeof( szEntryField2),
                                 szEntryField2 );
           }
           else
             WinPostMsg( hwndClient, DLGSAMP_EFCOMPLETE, 0L, 0L );

         case DID_CANCEL:/* Escape key or CANCEL pushbutton pressed/selected *
           if( bModality )
             WinDismissDlg( hwndDlg,TRUE );
           else
             WinPostMsg( hwndClient, DLGSAMP_DESTROYDLG, 0L, 0L );
           return FALSE;
         default:
           break;
       }
       break;

     default:  /* Pass all other messages to the default dialog proc */
       return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
   }
   return FALSE;
 }

 /***********************************************************************
 *
 *  DlgProc:  fnwpAutoRadioButtonDlg
 *
 *  A dialog procedure which uses auto radio buttons to change the
 *  color of the Client Area window.
 *
 ***********************************************************************/

 MRESULT EXPENTRY fnwpAutoRadioButtonDlg( hwndDlg, message, mp1, mp2 )
 HWND    hwndDlg;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
   switch (message)
   {
     case WM_INITDLG:
       colorSave = colorClient;
       CenterDlgBox( hwndDlg );
       if ( colorClient == CLR_RED )
         WinPostMsg( WinWindowFromID( hwndDlg, RB_RED ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE, 0 ),
                     0L );

       if ( colorClient == CLR_GREEN )
         WinPostMsg( WinWindowFromID( hwndDlg, RB_GREEN ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE, 0 ),
                     0L );

       if ( colorClient == CLR_BLUE )
         WinPostMsg( WinWindowFromID( hwndDlg, RB_BLUE ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE, 0 ),
                     0L );

       break;
     case WM_CONTROL:
       if( SHORT2FROMMP( mp1 ) == BN_CLICKED )
         switch( SHORT1FROMMP( mp1 ) )
         {
           case RB_RED:
             colorClient = CLR_RED;
             break;
           case RB_GREEN:
             colorClient = CLR_GREEN;
             break;
           case RB_BLUE:
             colorClient = CLR_BLUE;
             break;
           default:
            return FALSE;
         }
         WinInvalidateRect( hwndClient, NULL, FALSE );
         break;
     case WM_COMMAND:
       switch( SHORT1FROMMP( mp1 ) )
       {
         case DID_OK:     /* Enter key or pushbutton pressed/ selected
           if( !bModality )
             WinPostMsg( hwndClient, DLGSAMP_RBCOMPLETE, 0L, 0L );
           break;
         case DID_CANCEL: /* Escape key or CANCEL pushbutton pressed/selected
           colorClient = colorSave;
           break;
         default:
           return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
       }
       if( bModality )
         WinDismissDlg( hwndDlg, TRUE );
       else
         WinPostMsg( hwndClient, DLGSAMP_DESTROYDLG, 0L, 0L );
       break;

     default:  /* Pass all other messages to the default dialog proc */
       return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
   }
   return FALSE;
 }


 /***********************************************************************
 *
 *  DlgProc:  fnwpCheckBoxDlg
 *
 *  A dialog procedure to which use checkboxes to change the color
 *  of the Client Area Window.
 *
 ***********************************************************************/

 MRESULT EXPENTRY fnwpCheckBoxDlg( hwndDlg, message, mp1, mp2 )
 HWND    hwndDlg;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
   switch (message)
   {
     case WM_INITDLG:
       CenterDlgBox( hwndDlg );
       colorSave = colorClient;
       if( (colorClient & CLR_RED) == CLR_RED )
         WinPostMsg( WinWindowFromID( hwndDlg, CB_RED ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE,0 ),
                     0L );
       if( (colorClient & CLR_GREEN) == CLR_GREEN )
         WinPostMsg( WinWindowFromID( hwndDlg, CB_GREEN ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE,0 ),
                     0L );
       if( (colorClient & CLR_BLUE) == CLR_BLUE )
         WinPostMsg( WinWindowFromID( hwndDlg, CB_BLUE ),
                     BM_SETCHECK,
                     MPFROM2SHORT( TRUE,0 ),
                     0L );
       break;
     case WM_CONTROL:                  /* User has clicked on a checkbox */
       if( SHORT2FROMMP( mp1 ) == BN_CLICKED )
         CheckColor( hwndDlg, SHORT1FROMMP( mp1 ), &colorClient );
       WinInvalidateRect( hwndClient, NULL, FALSE );
       break;
     case WM_COMMAND:
       switch( SHORT1FROMMP( mp1 ) )
       {
         case DID_OK:     /* Enter key or pushbutton pressed/ selected */
           if( !bModality )
             WinPostMsg( hwndClient, DLGSAMP_CBCOMPLETE, 0L, 0L );
           break;
         case DID_CANCEL: /* Escape key or CANCEL pushbutton pressed/selected
           colorClient = colorSave;
           break;
         default:
           return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
       }
       if( bModality )
         WinDismissDlg( hwndDlg, TRUE );
       else
         WinPostMsg( hwndClient, DLGSAMP_DESTROYDLG, 0L, 0L );
       return FALSE;

     default:  /* Pass all other messages to the default dialog proc */
       return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
   }
   return FALSE;
 }

 /***********************************************************************
 *
 *  DlgProc:  fnwpListBoxDlg
 *
 ***********************************************************************/

 MRESULT EXPENTRY fnwpListBoxDlg( hwndDlg, message, mp1, mp2 )
 HWND    hwndDlg;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
   CHAR szBuffer[LEN_LISTBOXENTRY];
   SHORT  i;
   SHORT  id;

   switch (message)
   {
     case WM_INITDLG:
       CenterDlgBox( hwndDlg );

 /*************************************************************************
 *
 *   Initialize the listbox with a set of strings loaded from a
 *   resource file.
 *
 *************************************************************************/

       for ( i = 0; i < NUM_LISTBOXENTRIES; i++ )
       {
         WinLoadString( hab,
                        (HMODULE) NULL,
                        LBI_1 + i,
                        LEN_LISTBOXENTRY,
                        (PSZ)szBuffer
                      );
         WinSendDlgItemMsg( hwndDlg,
                            LB_1,
                            LM_INSERTITEM,
                            MPFROM2SHORT( LIT_END, 0 ),
                            MPFROMP( szBuffer )
                          );
       }
       break;
     case WM_COMMAND:
       switch( SHORT1FROMMP( mp1 ) )
       {
         case DID_OK:     /* Enter key or pushbutton pressed/ selected */
           if( bModality )
           {

 /***********************************************************************
 *
 *   Find out which item (if any) was selected and return the selected
 *   item text.
 *
 ***********************************************************************/

             id = SHORT1FROMMR( WinSendDlgItemMsg( hwndDlg,
                                                   LB_1,
                                                   LM_QUERYSELECTION,
                                                   0L,
                                                   0L ) );
             if( id == LIT_NONE )
               strcpy( szSelection, "" );
             else
               WinSendDlgItemMsg( hwndDlg,
                                  LB_1,
                                  LM_QUERYITEMTEXT,
                                  MPFROM2SHORT( id, LEN_LISTBOXENTRY ),
                                  MPFROMP( szSelection ) );
           }
           else
             WinPostMsg( hwndClient, DLGSAMP_LBCOMPLETE, 0L, 0L );
         case DID_CANCEL: /* Escape key or CANCEL pushbutton pressed/selected
           if( bModality )
             WinDismissDlg( hwndDlg, TRUE );
           else
             WinPostMsg( hwndClient, DLGSAMP_DESTROYDLG, 0L, 0L );
           return FALSE;
         default:
           break;
       }
       break;

     default:  /* Pass all other messages to the default dialog proc */
       return WinDefDlgProc( hwndDlg, message, mp1, mp2 );
   }
   return FALSE;
 }

 /*************************************************************************
 *
 *   FUNCTION : CenterDlgBox
 *
 *   Positions the dialog box in the center of the screen
 *
 *************************************************************************/

 VOID cdecl CenterDlgBox( hwnd )
 HWND hwnd;
 {
   SHORT ix, iy;
   SHORT iwidth, idepth;
   SWP   swp;

   iwidth = (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CXSCREEN );
   idepth = (SHORT)WinQuerySysValue( HWND_DESKTOP, SV_CYSCREEN );
   WinQueryWindowPos( hwnd, (PSWP)&swp );
   ix = ( iwidth  - swp.cx ) / 2;
   iy = ( idepth  - swp.cy ) / 2;
   WinSetWindowPos( hwnd, HWND_TOP, ix, iy, 0, 0, SWP_MOVE );
 }

 /***************************************************************************
 *
 *  FUNCTION: CheckColor
 *
 *  Toggle the Checked/UnChecked state of a checkbox and add/remove
 *  the corresponding CLR color component of the Client Area Color.
 *
 ***************************************************************************/

 VOID cdecl CheckColor( hwndDlg, iDlgItem, colorClient )
 HWND   hwndDlg;
 SHORT  iDlgItem;
 COLOR *colorClient;
 {
   BOOL  bChecked;
   COLOR color;

   switch( iDlgItem )
   {
     case CB_RED:
       color = CLR_RED;
       break;
     case CB_GREEN:
       color = CLR_GREEN;
       break;
     case CB_BLUE:
       color = CLR_BLUE;
       break;
     default:
       return;
   }

   bChecked = SHORT1FROMMR( WinSendMsg( WinWindowFromID( hwndDlg , iDlgItem ),
                                        BM_QUERYCHECK,
                                        0L,
                                        0L ) );
   WinPostMsg( WinWindowFromID( hwndDlg, iDlgItem ),
               BM_SETCHECK,
               MPFROM2SHORT( !bChecked, 0 ),
               0L );
   if( bChecked )                   /* If color previously checked */
     *colorClient -= color;         /* subtract it ... else        */
   else
     *colorClient += color;         /* ... add it.                 */
 }

 /**************************************************************************
 *
 *   FUNCTION: EnableModality
 *
 *   Enable or disable the Modality menuitems depending on the value
 *   of modal. This is done to prevent the user from altering the
 *   modality setting while a modeless dialog is active.
 *
 **************************************************************************/

 VOID cdecl EnableModality( hwnd, bModal )
 HWND hwnd;
 BOOL bModal;
 {
   if( bModal )
   {
     WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
                 MM_SETITEMATTR,
                 MPFROM2SHORT( MI_MODAL, TRUE ),
                 MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED ) );
     WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
                 MM_SETITEMATTR,
                 MPFROM2SHORT( MI_MODELESS, TRUE ),
                 MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED ) );
   }
   else
   {
     WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
                 MM_SETITEMATTR,
                 MPFROM2SHORT( MI_MODAL, TRUE ),
                 MPFROM2SHORT( MIA_DISABLED, MIA_DISABLED ) );
     WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
                 MM_SETITEMATTR,
                 MPFROM2SHORT( MI_MODELESS, TRUE ),
                 MPFROM2SHORT( MIA_DISABLED, MIA_DISABLED ) );
   }
 }

 /***************************************************************************
 *
 *  FUNCTION: IsIntInRange.
 *
 *  Checks whether the value of a dialog item is in an integer in
 *  a given range.
 *
 ***************************************************************************/

 BOOL cdecl IsIntInRange( hwndDlg,  idEntryField,
                          iLoRange, iHiRange,
                          idErrMsg, idMessageBox )
 HWND hwndDlg;
 SHORT  idEntryField;
 SHORT  iLoRange;
 SHORT  iHiRange;
 SHORT  idErrMsg;
 SHORT  idMessageBox;
 {
   SHORT ivalue;
   CHAR  szErrMsg[80];

 /****************************************************************************
 *
 *   Validate an entry field.
 *
 *   If validation fails leave the dialog visible, issue an error message
 *   using a messagebox, and when the user dismisses the messagebox,
 *   set the input focus to the entry field containing the error. Leave
 *   the contents of the entry field unchanged, and return FALSE.
 *
 *   If validation is successful return the value in ivalue and return
 *   TRUE.
 *
 ****************************************************************************/

   if( !WinQueryDlgItemShort( hwndDlg, idEntryField, &ivalue, TRUE ) ||
       ( ivalue < iLoRange ) ||
       ( ivalue > iHiRange ) )
   {
     WinLoadString( hab, (HMODULE) NULL, idErrMsg, sizeof( szErrMsg ), szErrMs
     WinMessageBox( HWND_DESKTOP,
                    hwndFrame,
                    (PSZ)szErrMsg,
                    NULL,
                    idMessageBox,
                    MB_OK );
     WinSetFocus( HWND_DESKTOP, WinWindowFromID( hwndDlg, idEntryField ) );
     return FALSE;
   }
   else
     return TRUE;
 }

 /***********************************************************************
 *
 *  FUNCTION: LoadDialog
 *
 *  Use the appropriate functions to put up a modal or modeless
 *  dialog box depending on the setting of the bModality parameter.
 *
 ***********************************************************************/

 VOID cdecl LoadDialog( hwndParent, hwndOwner, idDlg, fnwpDlgProc, bModality )
 HWND  hwndParent;
 HWND  hwndOwner;
 SHORT idDlg;
 PFNWP fnwpDlgProc;
 BOOL  bModality;
 {
   EnableModality( hwndOwner, FALSE ); /* Disable the Modality menu item */

   if( bModality )
   {
     WinDlgBox( hwndParent,        /* Parent                    */
                hwndOwner,         /* Owner                     */
                fnwpDlgProc,       /* Address of dialog proc    */
                (HMODULE) NULL,              /* Module handle             */
                idDlg,             /* Id of dialog in resource  */
                NULL );            /* Initialisation data       */
     EnableModality( hwndOwner, TRUE ); /* Enable the Modality menu item */
   }
   else
   {
     /*******************************************************************
     *
     *   Check to see if a modeless dialog is already running: if
     *   so destroy it before for loading the requested dialog. Save
     *   the handle of the new dialog in a global variable so that in
     *   can be accessed by the WinProc that issued LoadDialog.
     *
     *******************************************************************/

     if( WinIsWindow( hab, hwndModelessDlg ) )
       WinDestroyWindow( hwndModelessDlg );
     hwndModelessDlg = WinLoadDlg( hwndParent,
                                   hwndOwner,
                                   fnwpDlgProc,
                                   (HMODULE) NULL,
                                   idDlg,
                                   NULL );
   }
 }

 /*************************************************************************
 *
 *   FUNCTION: MainWndCommand
 *
 *   Take the appropriate action when a WM_COMMAND message is received by
 *   MainWndProc.  Issues calls which load dialogs in the prevailing state
 *   of modality.
 *
 *************************************************************************/

 VOID cdecl MainWndCommand( hwnd, Command, bModality )
 HWND   hwnd;
 USHORT Command;
 BOOL   *bModality;
 {
   USHORT idDlg;
   PFNWP  pfnDlgProc;

   switch( Command )
   {
     case MI_MODAL:
     case MI_MODELESS:
       *bModality = ( Command == MI_MODAL ) ? TRUE
                                            : FALSE;
       SetModality( WinQueryWindow( hwnd, QW_PARENT, FALSE ), *bModality );
       WinInvalidateRect( hwnd, NULL, FALSE );
       return;
     case MI_ENTRYFIELDEXAMPLE:
       idDlg      = DLG_ENTRYFIELDEXAMPLE;
       pfnDlgProc = (PFNWP)fnwpEntryFieldDlg;
       break;
     case MI_AUTORADIOBUTTONEXAMPLE:
       idDlg      = DLG_AUTORADIOBUTTONEXAMPLE;
       pfnDlgProc = (PFNWP)fnwpAutoRadioButtonDlg;
       break;
     case MI_CHECKBOXEXAMPLE:
       idDlg      = DLG_CHECKBOXEXAMPLE;
       pfnDlgProc = (PFNWP)fnwpCheckBoxDlg;
       break;
     case MI_LISTBOXEXAMPLE:
       idDlg      = DLG_LISTBOXEXAMPLE;
       pfnDlgProc = (PFNWP)fnwpListBoxDlg;
       break;
     case MI_ABOUTBOX:
       WinDlgBox(HWND_DESKTOP, hwnd, fnwpAboutBoxDlg, (HMODULE) NULL, DLG_ABOU
       return;
     default:
       return;
   }
   LoadDialog( HWND_DESKTOP,
               hwndFrame,
               idDlg,
               pfnDlgProc,
               *bModality );
   if( *bModality )
     WinInvalidateRect( hwnd, NULL, FALSE );  /* Request whole window repaint
 }

 /************************************************************************
 *
 *   FUNCTION: MainWndPaint
 *
 *   An unsophisticated window painting routine which simply repaints the
 *   entire window when a WM_PAINT message is received. In a real
 *   application more sophisticated techniques could be used to determine
 *   the minimum region needing repainting, and to paint only that
 *   region
 *
 ************************************************************************/

 VOID cdecl MainWndPaint( hwnd )
 HWND hwnd;
 {
   POINTL   pointl;
   HPS      hps;                          /* Presentation space handle */
   RECTL    rcl;                          /* Window rectangle          */
   CHAR     string[50];

   hps = WinBeginPaint( hwnd, (HPS)NULL, (PRECTL)&rcl );
   /*
         Color in the background
   */
   switch ((int) colorClient) {
         case 0:                /* (r,g,b) = (0,0,0) */
             WinFillRect( hps, (PRECTL)&rcl, CLR_BLACK );
             break;
         case 7:                /* (r,g,b) = (1,1,1) */
             WinFillRect( hps, (PRECTL)&rcl, CLR_WHITE );
             break;
         default:
             WinFillRect( hps, (PRECTL)&rcl, colorClient );
             break;
   }
   /*
         Set the text character colors
   */
   GpiSetColor( hps, (colorClient == 0L) ? CLR_WHITE
                                         : CLR_BLACK );
   pointl.x = 10L; pointl.y = 70L;
   strcpy( string, "Dialog modality    = " );
   strcat( string, (bModality) ? "Modal"
                               : "Modeless" );
   GpiCharStringAt( hps, &pointl, (LONG)strlen( string ), (PSZ)string );
   pointl.y = 50L;
   strcpy( string, "Entry Field 1      = " );
   strcat( string, szEntryField1 );
   GpiCharStringAt( hps, &pointl, (LONG)strlen( string ), (PSZ)string );
   pointl.y = 30L;
   strcpy( string, "Entry Field 2      = " );
   strcat( string, szEntryField2 );
   GpiCharStringAt( hps, &pointl, (LONG)strlen( string ), (PSZ)string );
   pointl.y = 10L;
   strcpy( string, "List Box Selection = " );
   strcat( string, szSelection );
   GpiCharStringAt( hps, &pointl, (LONG)strlen( string ), (PSZ)string );
   WinEndPaint( hps );
  }

 /**************************************************************************
 *
 *  FUNCTION: SetModality
 *
 *  Check or uncheck Modal and Modeless menu items as appropriate.
 *
 **************************************************************************/

 VOID cdecl SetModality( hwnd, bModal )
 HWND hwnd;
 BOOL bModal;
 {
   WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
               MM_SETITEMATTR,
               MPFROM2SHORT( MI_MODAL, TRUE ),
               MPFROM2SHORT( MIA_CHECKED, (bModal) ? ( MIA_CHECKED)
                                                   : (~MIA_CHECKED) ) );

   WinPostMsg( WinWindowFromID( hwnd, FID_MENU ),
               MM_SETITEMATTR,
               MPFROM2SHORT( MI_MODELESS, TRUE ),
               MPFROM2SHORT( MIA_CHECKED, (bModal) ? (~MIA_CHECKED)
                                                   : ( MIA_CHECKED) ) );

 }

 MRESULT EXPENTRY fnwpAboutBoxDlg(hDlg, msg, mp1, mp2)
 /*
     About... dialog procedure
 */
 HWND        hDlg;
 USHORT        msg;
 MPARAM        mp1;
 MPARAM        mp2;
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 DMGDB.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGDB.C

 /****************************** Module Header ******************************\
 * Module Name: DMGDB.C
 *
 * DDE manager data handling routines
 *
 * Created: 12/14/88 Sanford Staab
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddemlp.h"

 /***************************** Private Function ****************************\
 * PAPPINFO GetCurrentAppInfo()
 *
 * DESCRIPTION:
 * This routine uses the pid of the current thread to locate the information
 * pertaining to that thread.  If not found, 0 is returned.
 *
 * This call fails if the DLL is in a callback state to prevent recursion.
 * if fChkCallback is set.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 PAPPINFO GetCurrentAppInfo(fChkCallback)
 BOOL fChkCallback;
 {
     PAPPINFO pai;

     SemEnter();
     if (pAppInfoList == NULL || !CheckSel(SELECTOROF(pAppInfoList))) {
         SemLeave();
         return(0);
     }
     pai = pAppInfoList;
     while (pai) {
         if (pai->pid == FSRSemDmg.pid && pai->tid == FSRSemDmg.tid) {
             if (fChkCallback && pai->cInCallback > MAX_RECURSE) {
                 pai->LastError = DMGERR_REENTRANCY;
                 break;
             } else {
                 SemLeave();
                 return(pai);
             }
         }
         pai = pai->next;
     }
     SemLeave();
     return(0);
 }


 /***************************** Private Function ****************************\
 * void UnlinkAppInfo(pai)
 * PAPPINFO pai;
 *
 * DESCRIPTION:
 *   unlinks an pai safely.  Does nothing if not linked.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 void UnlinkAppInfo(pai)
 PAPPINFO pai;
 {
     PAPPINFO paiT;

     AssertF(pai != NULL, "UnlinkAppInfo - NULL input");
     SemEnter();
     if (pai == pAppInfoList) {
         pAppInfoList = pai->next;
         SemLeave();
         return;
     }
     paiT = pAppInfoList;
     while (paiT && paiT->next != pai)
         paiT = paiT->next;
     if (paiT)
         paiT->next = pai->next;
     SemLeave();
     return;
 }





 /***************************** Private Functions ***************************\
 * General List management functions.
 *
 * History:
 *   Created     12/15/88    sanfords
 \***************************************************************************/
 PLST CreateLst(hheap, cbItem)
 HHEAP hheap;
 USHORT cbItem;
 {
     PLST pLst;

     SemEnter();
     if (!(pLst = (PLST)FarAllocMem(hheap, sizeof(LST)))) {
         SemLeave();
         return(NULL);
     }
     pLst->hheap = hheap;
     pLst->cbItem = cbItem;
     pLst->pItemFirst = (PLITEM)NULL;
     SemLeave();
     return(pLst);
 }



 void FlushLst(pLst)
 PLST pLst;
 {
     if (pLst == NULL)
         return;
     SemEnter();
     while (pLst->pItemFirst)
         RemoveLstItem(pLst, pLst->pItemFirst);
     SemLeave();
 }



 void DestroyLst(pLst)
 PLST pLst;
 {
     if (pLst == NULL)
         return;
     SemEnter();
     while (pLst->pItemFirst)
         RemoveLstItem(pLst, pLst->pItemFirst);
     FarFreeMem(pLst->hheap, pLst, sizeof(LST));
     SemLeave();
 }



 PLITEM FindLstItem(pLst, npfnCmp, piSearch)
 PLST pLst;
 NPFNCMP npfnCmp;
 PLITEM piSearch;
 {
     PLITEM pi;

     if (pLst == NULL)
         return(NULL);
     SemEnter();
     pi = pLst->pItemFirst;
     while (pi) {
         if ((*npfnCmp)
                 ((PBYTE)pi + sizeof(LITEM), (PBYTE)piSearch + sizeof(LITEM)))
             SemLeave();
             return(pi);
         }
         pi = pi->next;
     }
     SemLeave();
 }



 /*
  * Comparison functions for FindLstItem() and FindPileItem()
  */

 BOOL CmpULONG(pb1, pb2)
 PBYTE pb1;
 PBYTE pb2;
 {
     return(*(PULONG)pb1 == *(PULONG)pb2);
 }

 BOOL CmppHsz(pb1, pb2)
 PBYTE pb1;
 PBYTE pb2;
 {
     return(CmpHsz(*(PHSZ)pb1, *(PHSZ)pb2) ? FALSE : TRUE);
 }




 /***************************** Private Function ****************************\
 * This routine creates a new list item for pLst and links it in according
 * to the ILST_ constant in afCmd.  Returns a pointer to the new item
 * or NULL on failure.
 *
 * Note:  This MUST be in the semaphore for use since the new list item
 * is filled with garbage on return yet is linked in.
 *
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 PLITEM NewLstItem(pLst, afCmd)
 PLST pLst;
 USHORT afCmd;
 {
     PLITEM pi, piT;

     if (pLst == NULL)
         return(NULL);
     SemCheckIn();

     pi = (PLITEM)FarAllocMem(pLst->hheap, pLst->cbItem + sizeof(LITEM));
     if (pi == NULL) {
         AssertF(FALSE, "NewLstItem - memory failure");
         return(NULL);
     }

     if (afCmd & ILST_NOLINK)
         return(pi);

     if (((piT = pLst->pItemFirst) == NULL) || (afCmd & ILST_FIRST)) {
         pi->next = piT;
         pLst->pItemFirst = pi;
     } else {                            /* ILST_LAST assumed */
         while (piT->next != NULL)
             piT = piT->next;
         piT->next = pi;
         pi->next = NULL;
     }
     return(pi);
 }



 /***************************** Private Function ****************************\
 * This routine unlinks and frees pi from pLst.  If pi cannot be located
 * within pLst, it is freed anyway.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 BOOL RemoveLstItem(pLst, pi)
 PLST pLst;
 PLITEM pi;
 {
     PLITEM piT;

     if (pLst == NULL || pi == NULL)
         return(FALSE);

     SemCheckIn();

     if ((piT = pLst->pItemFirst) != NULL) {
         if (pi == piT) {
             pLst->pItemFirst = pi->next;
         } else {
             while (piT->next != pi && piT->next != NULL)
                 piT = piT->next;
             if (piT->next != NULL)
                 piT->next = pi->next; /* unlink */
         }
     } else {
         AssertF(pi == NULL, "Improper list item removal");
     }
     FarFreeMem(pLst->hheap, pi, pLst->cbItem + sizeof(LITEM));
     return(TRUE);
 }



 /***************************** Private Function ****************************\
 * This routine uses ILST_ constants to insert a list item into the apropriate
 * spot of the pLst given.  Only ILST_FIRST or ILST_LAST are allowed.
 *
 * History:
 *   Created     9/11/89    Sanfords
 \***************************************************************************/
 BOOL InsertLstItem(pLst, pi, afCmd)
 PLST pLst;
 PLITEM pi;
 USHORT afCmd;
 {
     PLITEM piT;

     if (pLst == NULL)
         return(FALSE);

     SemEnter();

     if (pLst->pItemFirst == NULL || afCmd & ILST_FIRST) {
         pi->next = pLst->pItemFirst;
         pLst->pItemFirst = pi;
     } else {                    /* ILST_LAST assumed */
         piT = pLst->pItemFirst;
         while (piT->next)
             piT = piT->next;
         piT->next = pi;
         pi->next = NULL;
     }

     SemLeave();
     return(TRUE);
 }




 /*
  * ------------- Specific list routines -------------
  */

 /*
  * This function is HIGHLY dependent on the ADVLI structure.
  * This will match an exact hsz/fmt pair with a 0 format being wild.
  */
 BOOL CmpAdv(pb1, pb2)
 PBYTE pb1;
 PBYTE pb2;
 {
     USHORT usFmt;

     if (*(PHSZ)pb1 == *(PHSZ)pb2) {
         if ((usFmt = *(PUSHORT)(pb2 + 4)) == 0)
             return(TRUE);
         if (usFmt == *(PUSHORT)(pb1 + 4))
             return(TRUE);
     }
     return(FALSE);
 }



 BOOL fSearchHwndList(pLst, hwnd)
 PLST pLst;
 HWND hwnd;
 {
     HWNDLI hwndi;

     hwndi.hwnd = hwnd;
     return((BOOL)FindLstItem(pLst, CmpHwnd, (PLITEM)&hwndi));
 }



 void AddHwndList(hwnd, pLst)
 HWND hwnd;
 PLST pLst;
 {
     HWNDLI hwndli;
     PHWNDLI pli;

     AssertF(pLst != NULL, "AddHwndList - NULL pLst");
     AssertF(pLst->cbItem == sizeof(HWNDLI), "AddHwndList - Bad item size");
     SemEnter();
     hwndli.hwnd = hwnd;
     if ((hwnd == NULL) || FindLstItem(pLst, CmpHwnd, (PLITEM)&hwndli)) {
         SemLeave();
         return;
     }
     pli = (PHWNDLI)NewLstItem(pLst, ILST_FIRST);
     pli->hwnd = hwnd;
     SemLeave();
 }



 /*
  * Insert the given data into the list if one does not already exist
  * under the given hwnd.
  */
 void AddAckHwndList(hwnd, hszApp, hszTopic, pLst)
 HWND hwnd;
 HSZ hszApp;
 HSZ hszTopic;
 PLST pLst;
 {
     HWNDLI hwndli;
     PACKHWNDLI pli;

     AssertF(pLst != NULL, "AddAckHwndList - NULL pLst");
     AssertF(pLst->cbItem == sizeof(ACKHWNDLI), "AddAckHwndList - Bad item siz
     SemEnter();
     hwndli.hwnd = hwnd;
     if ((hwnd == NULL) || FindLstItem(pLst, CmpHwnd, (PLITEM)&hwndli)) {
         SemLeave();
         return;
     }
     pli = (PACKHWNDLI)NewLstItem(pLst, ILST_FIRST);
     pli->hwnd = hwnd;
     pli->hszApp = hszApp;
     pli->hszTopic = hszTopic;
     SemLeave();
 }




 /***************************** Private Function ****************************\
 * hwnd-hsz list functions
 *
 * History:      1/20/89     Created         sanfords
 \***************************************************************************/
 void AddHwndHszList(hsz, hwnd, pLst)
 HSZ hsz;
 HWND hwnd;
 PLST pLst;
 {
     PHWNDHSZLI phhi;

     AssertF(pLst->cbItem == sizeof(HWNDHSZLI), "AddHwndHszList - Bad item siz
     SemEnter();
     if ((hsz == NULL) || (BOOL)HwndFromHsz(hsz, pLst)) {
         SemLeave();
         return;
     }
     phhi = (PHWNDHSZLI)NewLstItem(pLst, ILST_FIRST);
     phhi->hwnd = hwnd;
     phhi->hsz = hsz;
     IncHszCount(hsz);
     SemLeave();
 }


 void DestroyHwndHszList(pLst)
 PLST pLst;
 {
     AssertF(pLst->cbItem == sizeof(HWNDHSZLI), "DestroyHwndHszList - Bad item
     SemEnter();
     while(pLst->pItemFirst) {
         FreeHsz(((PHWNDHSZLI)pLst->pItemFirst)->hsz);
         RemoveLstItem(pLst, pLst->pItemFirst);
     }
     FarFreeMem(pLst->hheap, pLst, sizeof(LST));
     SemLeave();
 }



 HWND HwndFromHsz(hsz, pLst)
 HSZ hsz;
 PLST pLst;
 {
     HWNDHSZLI hhli;
     PHWNDHSZLI phhli;

     hhli.hsz = hsz;
     if (!(phhli = (PHWNDHSZLI)FindLstItem(pLst, CmppHsz, (PLITEM)&hhli)))
         return(NULL);
     return(phhli->hwnd);
 }



 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   Advise list helper functions.
 *
 * History:      1/20/89     Created         sanfords
 \***************************************************************************/
 BOOL AddAdvList(pLst, hszItem, fsStatus, usFmt)
 PLST pLst;
 HSZ hszItem;
 USHORT fsStatus;
 USHORT usFmt;
 {
     PADVLI pali;

     AssertF(pLst->cbItem == sizeof(ADVLI), "AddAdvList - bad item size");
     if (hszItem == NULL)
         return(TRUE);
     SemEnter();
     if (!(pali = FindAdvList(pLst, hszItem, usFmt))) {
         IncHszCount(hszItem);
         pali = (PADVLI)NewLstItem(pLst, ILST_FIRST);
     }
     AssertF((BOOL)pali, "AddAdvList - NewLstItem() failed")
     if (pali != NULL) {
         pali->hszItem = hszItem;
         pali->usFmt = usFmt;
         pali->fsStatus = fsStatus;
     }
     SemLeave();
     return((BOOL)pali);
 }



 /*
  * This will delete the matching Advise loop entry.  If usFmt is 0, all
  * entries with the same hszItem are deleted.  Returns fNotEmptyAfterDelete.
  */
 BOOL DeleteAdvList(pLst, hszItem, usFmt)
 PLST pLst;
 HSZ hszItem;
 USHORT usFmt;
 {
     PADVLI pali;

     if (hszItem == NULL)
         return((BOOL)pLst->pItemFirst);
     SemEnter();
     while (pali = (PADVLI)FindAdvList(pLst, hszItem, usFmt)) {
         FreeHsz((pali)->hszItem);
         RemoveLstItem(pLst, (PLITEM)pali);
     }
     SemLeave();
     return((BOOL)pLst->pItemFirst);
 }



 /***************************** Private Function ****************************\
 * This routine searches the advise list for and entry in hszItem.  It returns
 * pAdvli only if the item is found.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 PADVLI FindAdvList(pLst, hszItem, usFmt)
 PLST pLst;
 HSZ hszItem;
 USHORT usFmt;
 {
     ADVLI advli;

     advli.hszItem = hszItem;
     advli.usFmt = usFmt;
     return((PADVLI)FindLstItem(pLst, CmpAdv, (PLITEM)&advli));
 }


 /***************************** Private Function ****************************\
 * This routine searches for the next entry for hszItem.  It returns
 * pAdvli only if the item is found.
 *
 * History:
 *   Created     11/15/89    Sanfords
 \***************************************************************************/
 PADVLI FindNextAdv(padvli, hszItem)
 PADVLI padvli;
 HSZ hszItem;
 {

     SemEnter();
     while ((padvli = (PADVLI)padvli->next) != NULL) {
         if (padvli->hszItem == hszItem) {
             SemLeave();
             return(padvli);
         }
     }
     SemLeave();
     return(NULL);
 }



 /***************************** Pile Functions *******************************
 *
 *  A pile is a list where each item is an array of subitems.  This allows
 *  a more memory efficient method of handling unordered lists.
 *
 \****************************************************************************

 PPILE CreatePile(hheap, cbItem, cItemsPerBlock)
 HHEAP hheap;
 USHORT cbItem;
 USHORT cItemsPerBlock;
 {
     PPILE ppile;

     if (!(ppile = (PPILE)FarAllocMem(hheap, sizeof(PILE)))) {
         SemLeave();
         return(NULL);
     }
     ppile->pBlockFirst = (PLITEM)NULL;
     ppile->hheap = hheap;
     ppile->cbBlock = cbItem * cItemsPerBlock + sizeof(PILEB);
     ppile->cSubItemsMax = cItemsPerBlock;
     ppile->cbSubItem = cbItem;
     return(ppile);
 }


 PPILE DestroyPile(pPile)
 PPILE pPile;
 {
     if (pPile == NULL)
         return(NULL);
     SemEnter();
     while (pPile->pBlockFirst)
         RemoveLstItem((PLST)pPile, (PLITEM)pPile->pBlockFirst);
     FarFreeMem(pPile->hheap, pPile, sizeof(PILE));
     SemLeave();
     return(NULL);
 }

 void FlushPile(pPile)
 PPILE pPile;
 {
     if (pPile == NULL)
         return;
     SemEnter();
     while (pPile->pBlockFirst)
         RemoveLstItem((PLST)pPile, (PLITEM)pPile->pBlockFirst);
     SemLeave();
 }


 USHORT QPileItemCount(pPile)
 PPILE pPile;
 {
     register USHORT c;
     PPILEB pBlock;

     if (pPile == NULL)
         return(0);

     SemEnter();
     pBlock = pPile->pBlockFirst;
     c = 0;
     while (pBlock) {
         c += pBlock->cItems;
         pBlock = pBlock->next;
     }
     SemLeave();
     return(c);
 }


 BOOL CopyPileItems(pPile, pDst)
 PPILE pPile;
 PBYTE pDst;
 {
     PPILEB pBlock;

     AssertF(pDst != NULL, "CopyPileItems - NULL destination");
     if (pPile == NULL)
         return(FALSE);

     SemEnter();
     pBlock = pPile->pBlockFirst;
     while (pBlock) {
         CopyBlock((PBYTE)pBlock + sizeof(PILEB), pDst,
                 pBlock->cItems * pPile->cbSubItem);
         pDst += pBlock->cItems * pPile->cbSubItem;
         pBlock = pBlock->next;
     }
     SemLeave();

     return(TRUE);
 }




 /***************************** Private Function ****************************\
 * Locate and return the pointer to the pile subitem who's key fields match
 * pbSearch using npfnCmp to compare the fields.  If pbSearch == NULL, or
 * npfnCmp == NULL, the first subitem is returned.
 *
 * afCmd may be:
 * FPI_DELETE - delete the located item
 * FPI_COUNT - count number of items that match
 * In this case, the returned pointer is not valid.
 *
 * pppb points to where to store a pointer to the block which contained
 * the located item.
 *
 * if pppb == NULL, it is ignored.
 *
 * NULL is returned if pbSearch was not found or if the list was empty.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 PBYTE FindPileItem(pPile, npfnCmp, pbSearch, afCmd)
 PPILE pPile;
 NPFNCMP npfnCmp;
 PBYTE pbSearch;
 USHORT afCmd;
 {
     PBYTE pb;
     PPILEB ppbT;
     register int i;
     register int c;

     if (pPile == NULL)
         return(NULL);
     c = 0;
     SemEnter();
     ppbT = pPile->pBlockFirst;
     while (ppbT) {
         /*
          * for each block...
          */
         for (pb = (PBYTE)ppbT + sizeof(PILEB), i = 0;
                 i < ppbT->cItems; pb += pPile->cbSubItem, i++) {
             /*
              * and each item within that block..
              */
             if (pbSearch == NULL || npfnCmp == NULL ||
                     (*npfnCmp)(pb, pbSearch)) {
                 /*
                  * If it matches or we don't care...
                  */
                 if (afCmd & FPI_DELETE) {
                     /*
                      * remove entire block if this was the last subitem in it
                      */
                     if (--ppbT->cItems == 0) {
                         RemoveLstItem((PLST)pPile, (PLITEM)ppbT);
                     } else {
                         /*
                          * copy last subitem in the block over the removed it
                          */
                         CopyBlock((PBYTE)ppbT + sizeof(PILEB) +
                                 pPile->cbSubItem * ppbT->cItems,
                                 pb, pPile->cbSubItem);
                     }
                 }
                 if (afCmd & FPI_COUNT) {
                     c++;
                 } else {
                     SemLeave();
                     return(pb);
                 }
                 if (afCmd & FPI_DELETE) {
                     pb = (PBYTE)ppbT + sizeof(PILEB);
                     i = 0;
                 }
             }
         }
         ppbT = (PPILEB)ppbT->next;
     }
     SemLeave();
     return((PBYTE)(ULONG)c);
 }


 /***************************** Private Function ****************************\
 * Places a copy of the subitem pointed to by pb into the first available
 * spot in the pile pPile.  If npfnCmp != NULL, the pile is first searched
 * for a pb match.  If found, pb replaces the located data but FALSE is
 * returned to show that no real addition took place.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 BOOL AddPileItem(pPile, pb, npfnCmp)
 PPILE pPile;
 PBYTE pb;
 BOOL (*npfnCmp)(PBYTE pb, PBYTE pbSearch);
 {
     PBYTE pbDst;
     PPILEB ppb;

     if (pPile == NULL)
         return(FALSE);
     SemEnter();
     if (npfnCmp != NULL &&
             (pbDst = FindPileItem(pPile, npfnCmp, pb, 0)) != NULL) {
         CopyBlock(pb, pbDst, pPile->cbSubItem);
         SemLeave();
         return(FALSE);
     }
     ppb = pPile->pBlockFirst;
     /*
      * locate a block with room
      */
     while ((ppb != NULL) && ppb->cItems == pPile->cSubItemsMax) {
         ppb = (PPILEB)ppb->next;
     }
     /*
      * If all full or no blocks, make a new one, link it on the bottom.
      */
     if (ppb == NULL) {
         if ((ppb = (PPILEB)NewLstItem((PLST)pPile, ILST_LAST)) == NULL) {
             SemLeave();
             return(FALSE);
         }
         ppb->cItems = 0;
     }
     /*
      * add the subitem
      */
     CopyBlock(pb, (PBYTE)ppb + sizeof(PILEB) + pPile->cbSubItem * ppb->cItems
         pPile->cbSubItem);

     SemLeave();
     return(TRUE);
 }




 /***************************** Private Function ****************************\
 * Fills pb with a copy of the top item's data and removes it from the pile.
 * returns FALSE if the pile was empty.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 BOOL PopPileSubitem(pPile, pb)
 PPILE pPile;
 PBYTE pb;
 {
     PPILEB ppb;
     PBYTE pSrc;


     if ((pPile == NULL) || ((ppb = pPile->pBlockFirst) == NULL))
         return(FALSE);

     SemEnter();
     pSrc = (PBYTE)pPile->pBlockFirst + sizeof(PILEB);
     CopyBlock(pSrc, pb, pPile->cbSubItem);
     /*
      * remove entire block if this was the last subitem in it.
      */
     if (pPile->pBlockFirst->cItems == 1) {
         RemoveLstItem((PLST)pPile, (PLITEM)pPile->pBlockFirst);
     } else {
         /*
          * move last item in block to replace copied subitem and decrement
          * subitem count.
          */
         CopyBlock(pSrc + pPile->cbSubItem * --pPile->pBlockFirst->cItems,
                 pSrc, pPile->cbSubItem);
     }
     SemLeave();
     return(TRUE);
 }


 /***************************** Semaphore Functions *************************\
 * SemEnter() and SemLeave() are macros.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 void SemInit()
 {
     PBYTE pSem;
     SHORT c;

     pSem = (PBYTE)&FSRSemDmg;
     c = 0;
     while (c++ < sizeof(DOSFSRSEM)) {
         *pSem++ = 0;
     }
     FSRSemDmg.cb = sizeof(DOSFSRSEM);
 }

 #ifdef DEBUG
 void SemCheckIn()
 {
     PIDINFO pi;
     BOOL fin;

     DosGetPID(&pi);
     fin = (FSRSemDmg.cUsage > 0) &&
             (FSRSemDmg.pid == pi.pid) &&
             ((FSRSemDmg.tid == pi.tid) || (FSRSemDmg.tid == -1));
     /*
      * !!! NOTE: during exitlists processing, semaphore TIDs are set to -1
      */
     AssertF(fin, "SemCheckIn - Out of Semaphore");
     if (!fin)
         SemEnter();
 }

 void SemCheckOut()
 {
     PIDINFO pi;
     BOOL fOut;

     DosGetPID(&pi);
     fOut = FSRSemDmg.cUsage == 0 || FSRSemDmg.pid != pi.pid ||
                 FSRSemDmg.tid != pi.tid;
     AssertF(fOut, "SemCheckOut - In Semaphore");
     if (!fOut)
         while (FSRSemDmg.cUsage)
             SemLeave();

 }
 #endif


 void SemEnter()
 {
     DosFSRamSemRequest(&FSRSemDmg, SEM_INDEFINITE_WAIT);
 }


 void SemLeave()
 {
     DosFSRamSemClear(&FSRSemDmg);
 }



 void EXPENTRY ExlstAbort(usTermCode)
 USHORT usTermCode;
 {
     PAPPINFO pai;
     usTermCode;

     SemEnter();     /* get any other processes out of the semaphore */
     if (pai = GetCurrentAppInfo(FALSE)) {
         pai->cInCallback = 0;  /* so Unregister call will work */
         DdeUninitialize();
     } else {
         SemLeave();
         DosExitList(EXLST_REMOVE, (PFNEXITLIST)ExlstAbort);
     }
     DosExitList(EXLST_EXIT, 0);
 }

 BOOL CopyHugeBlock(pSrc, pDst, cb)
 PBYTE pSrc;
 PBYTE pDst;
 ULONG cb;
 {
     ULONG cFirst;
     /*
      *  |____________|   |___________|   |____________|  |____________|
      *     ^src                                 ^
      *
      *  |____________|   |___________|   |____________|  |____________|
      *             ^dst                                   ^
      */
     cFirst = (ULONG)min((~(USHORT)pSrc), (~(USHORT)pDst)) + 1L;
     if (cb < cFirst) {
         CopyBlock(pSrc, pDst, (USHORT)cb);
         return(TRUE);
     }

     goto copyit;

     /*
      * Now at least one of the pointers is on a segment boundry.
      */
     while (cb) {
         cFirst = min(0x10000 - ((USHORT)pSrc | (USHORT)pDst), cb);
 copyit:
         if (HIUSHORT(cFirst)) {
             /*
              * special case where pSrc and pDst both are on segment
              * bounds.  Copy half at a time.
              */
             /*
              *  |___________|   |____________|  |____________|
              *  ^src                               ^
              *
              *  |___________|   |____________|  |____________|
              *  ^dst                               ^
              */
             cFirst >>= 1;
             CopyBlock(pSrc, pDst, (USHORT)cFirst);
             pSrc += cFirst;
             pDst += cFirst;
             cb -= cFirst;
         }
         CopyBlock(pSrc, pDst, (USHORT)cFirst);
         pSrc = HugeOffset(pSrc, cFirst);
         pDst = HugeOffset(pDst, cFirst);
         cb -= cFirst;
     /*
      *  |____________|   |___________|   |____________|  |____________|
      *           ^src                           ^
      *
      *  |____________|   |___________|   |____________|  |____________|
      *                   ^dst                             ^
      */
     }
     return(TRUE);
 }




 /***************************************************************************\
 * Kills windows but avoids invalid window rips in debugger.
 \***************************************************************************/
 BOOL DestroyWindow(hwnd)
 HWND hwnd;
 {
     if (WinIsWindow(DMGHAB, hwnd))
         return(WinDestroyWindow(hwnd));
     return(TRUE);
 }


 /***************************** Private Function ****************************\
 * Returns hConv of the window passed in is one of the ddeml windows.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 HCONV IsDdeWindow(hwnd)
 HWND hwnd;
 {
     PAPPINFO pai;

     pai = pAppInfoList;

     while (pai && WinIsChild(hwnd, pai->hwndDmg))
         pai = pai->next;

     if (pai)
         return((HCONV)hwnd);
     else
         return(0L);
 }


 /***************************** Private Function ****************************\
 * This routine only frees a MYDDES segment if this process is not the owner.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 void FreeData(
 PMYDDES pmyddes,
 PAPPINFO pai)
 {
     TID tid;
     if (!CheckSel(SELECTOROF(pmyddes)) ||
             (   pmyddes->offszItemName == sizeof(MYDDES) &&
                 pmyddes->magic == MYDDESMAGIC &&
                 pmyddes->fs & HDATA_APPOWNED &&
                 pmyddes->pai == pai) )
         return;

     SemEnter();
     FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&pmyddes, FPI_DELETE);
     tid = pai->tid;
     do {
         if (FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&pmyddes, FPI_COUN
             SemLeave();
             return;
         }
         pai = pai->nextThread;
     } while (pai && pai->tid != tid);
     SemLeave();
     DosFreeSeg(SELECTOROF(pmyddes));
 }



 #ifdef DEBUG
 int APIENTRY DebugOutput(PCH);
 void fAssert(f, pszComment, line, szfile)
 BOOL f;
 PSZ pszComment;
 USHORT line;
 PSZ szfile;
 {
     char szT[90];
     PSZ psz, pszLast;

     if (!f) {
         szT[0] = '\000';
         psz = szT;
         pszLast = &szT[89];
         psz = lstrcat(psz, "\n\rAssertion failure: ", pszLast);
         psz = lstrcat(psz, szfile, pszLast);
         psz = lstrcat(psz, ":", pszLast);
         psz = dtoa(psz, line, FALSE);
         psz = lstrcat(psz, " ", pszLast);
         psz = lstrcat(psz, pszComment, pszLast);
         psz = lstrcat(psz, "\n\r", pszLast);
         DebugOutput(szT);
         DebugBreak();
     }
 }
 #endif



 HDMGDATA PutData(pSrc, cb, cbOff, hszItem, usFmt, afCmd, pai)
 PBYTE pSrc;
 ULONG cb;
 ULONG cbOff;
 HSZ hszItem;
 USHORT usFmt;
 USHORT afCmd;
 PAPPINFO pai;
 {
     PMYDDES pmyddes;

     if ((pmyddes = (PMYDDES)AllocDDESel(0, usFmt, hszItem, cb + cbOff, pai))
             == NULL) {
         pai->LastError = DMGERR_MEMORY_ERROR;
         return(0L);
     }
     pmyddes->fs = afCmd;

     if (afCmd & HDATA_APPFREEABLE) {
         if (!AddPileItem(pai->pHDataPile, (PBYTE)&pmyddes, CmpULONG)) {
             DosFreeSeg(SELECTOROF(pmyddes));
             pai->LastError = DMGERR_MEMORY_ERROR;
             return(0L);
         }
     }
     if (pSrc)
         CopyHugeBlock(pSrc, HugeOffset(DDES_PABDATA(pmyddes), cbOff), cb);
     return(pmyddes);
 }


 /*
  * This routine adds all HSZ/HAPP pairs it finds for the given pai matching
  * hszApp to hDataAdd.
  * poffAdd is the offset into the hDataAdd to start inserting HSZ/HAPP
  * pairs.  It then truncates the list with a 0 HSZ and returns the offset
  * to the terminator (ready to be called again to add more).
  *
  * returns 0L on error.
  */
 ULONG
 QueryAppNames(
 PAPPINFO pai,
 HDMGDATA hDataAdd,
 HSZ hszApp,
 ULONG offAdd)
 {
     USHORT chsz;
     PHSZ phsz, phszPile;
     PPILEB pBlock;

     AssertF(sizeof(HSZ) == sizeof(HAPP), "Type size conflict");

     SemEnter();
     if (chsz = (USHORT)FindPileItem(pai->pAppNamePile,
             hszApp ? CmpULONG : NULL, (PBYTE)&hszApp, FPI_COUNT)) {
         /*
          * allocate for additions.
          */
         if (!DdeAddData(hDataAdd, NULL,
                 (chsz + 1L) * (sizeof(HSZ) + sizeof(HDMGDATA)), offAdd)) {
             offAdd = 0L;
             GetCurrentAppInfo(FALSE)->LastError = DMGERR_MEMORY_ERROR;
             goto Exit;
         }

         phsz = DDES_PABDATA((PDDESTRUCT)hDataAdd) + offAdd;
         if (hszApp) {
             *phsz++ = hszApp;       /* only one per thread expected */
             *phsz++ = (HSZ)pai->hwndFrame;
         } else {
             pBlock = pai->pAppNamePile->pBlockFirst;
             while (pBlock) {
                 phszPile = (PHSZ)(pBlock + 1);
                 for (chsz = 0; chsz < pBlock->cItems; chsz++) {
                     *(phsz++) = *(phszPile++);
                     *(phsz++) = (HSZ)pai->hwndFrame;
                 }
                 pBlock = pBlock->next;
             }
         }
         *phsz = 0L;
         offAdd = phsz - DDES_PABDATA((PDDESTRUCT)hDataAdd);
     }
 Exit:
     SemLeave();
     return(offAdd);
 }



 DMGDDE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGDDE.C

 /****************************** Module Header ******************************\
 * Module Name: DMGDDE.C
 *
 * This module contains functions used for interfacing with DDE structures
 * and such.
 *
 * Created:  12/23/88    sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddemlp.h"

 /***************************** Private Function ****************************\
 * timeout()
 *
 * This routine creates a timer for hwndTimeout.  It then runs a modal loop
 * which will exit once the WM_TIMER message is received by hwndTimeout.
 * hwndTimeout can be any window that doesn't use timers itself with TID_TIMEO
 * or TID_ABORT since its window proc doesn't need to do
 * anything for this to work.  Only the client and server windows use these
 * so were cool.
 * Only one timeout window is allowed per thread.  This is checked by the
 * pai passed in.
 *
 * Returns fSuccess, ie TRUE if TID_TIMEOUT was received before TID_ABORT.
 *
 * PUBDOC START
 * Synchronous client transaction modal loops:
 *
 * During Synchronous transactions, a client application will enter a modal
 * loop while waiting for the server to respond to the request.  If an
 * application wishes to filter messages to the modal loop, it may do so
 * by setting a message filter tied to MSGF_DDE.  Applications should
 * be aware however that the DDE modal loop processes private messages
 * in the WM_USER range, WM_DDE messages, and WM_TIMER messages with timer IDs
 * using the TID_ constants defined in ddeml.h.
 * These messages must not be filtered by an application!!!
 *
 * PUBDOC END
 *
 * History:
 *   Created     sanfords    12/19/88
 \***************************************************************************/
 BOOL timeout(pai, ulTimeout, hwndTimeout)
 PAPPINFO pai;
 ULONG ulTimeout;
 HWND hwndTimeout;
 {
     QMSG qmsg;

     SemEnter();
     if (pai->hwndTimer) {
         pai->LastError = DMGERR_REENTRANCY;
         AssertF(FALSE, "Recursive timeout call");
         SemLeave();
         return(FALSE);
     }
     pai->hwndTimer = hwndTimeout;
     SemLeave();

     WinStartTimer(DMGHAB, hwndTimeout, TID_TIMEOUT, (USHORT)ulTimeout);

     WinGetMsg(DMGHAB, &qmsg, (HWND)NULL, 0, 0);

     /*
      * stay in modal loop until a timeout happens.
      */
     while (qmsg.hwnd != hwndTimeout ||
             qmsg.msg != WM_TIMER ||
             (LOUSHORT(qmsg.mp1) != TID_TIMEOUT &&
             LOUSHORT(qmsg.mp1) != TID_ABORT)) {

         if (!WinCallMsgFilter(DMGHAB, &qmsg, MSGF_DDE))
             WinDispatchMsg(DMGHAB, &qmsg);

         WinGetMsg(DMGHAB, &qmsg, (HWND)NULL, 0, 0);
     }

     WinStopTimer(DMGHAB, hwndTimeout, TID_TIMEOUT);
     SemEnter();
     pai->hwndTimer = 0;
     SemLeave();
     /*
      * post a callback check incase we blocked callbacks due to being
      * in a timeout.
      */
     WinPostMsg(pai->hwndDmg, UM_CHECKCBQ, (MPARAM)pai, 0L);
     return(LOUSHORT(qmsg.mp1) == TID_TIMEOUT);
 }


 /***************************** Private Function ****************************\
 *
 *  Based on pii, this sends an INITIATE message to either an exact
 *  target window (hwndSend), a target frame window (hwndFrame) or to all
 *  top level frames (both hwnds are NULL).  It fills in pci info as apropriat
 *  Note that pii->pCC must NOT be NULL and is assumed to be properly set.
 *
 *  Returns FALSE if SendDDEInit failed.
 *  On success pci->ci.xad.state is CONVST_CONNECTED.
 *
 * History:
 *   created 12/21/88        sanfords
 \***************************************************************************/
 BOOL ClientInitiate(hwnd, pii, pci)
 HWND hwnd;
 PINITINFO pii;
 PCLIENTINFO pci;
 {
     BOOL fRet = TRUE;

     if (pii->pCC->cb < sizeof(CONVCONTEXT))
         return(FALSE);

     SemEnter();
     /*
      * we need to set this info BEFORE we do the synchronous initiate
      * so the INITIATEACK msg is done correctly.
      */
     pci->ci.xad.state = CONVST_INIT1;
     pci->ci.xad.LastError = DMGERR_NO_ERROR;
     pci->ci.hszServerApp = pii->hszAppName;
     pci->ci.hszTopic = pii->hszTopic;
     pci->ci.cc.cb = sizeof(CONVCONTEXT);
     CopyBlock((PBYTE)&pii->pCC->fsContext, (PBYTE)&pci->ci.cc.fsContext,
         sizeof(CONVCONTEXT) - sizeof(USHORT));
     pci->ci.hwndFrame = pii->hwndFrame;
     SemLeave();

     fRet = SendDDEInit(hwnd,
             WinIsWindow(DMGHAB, pii->hwndSend) ? pii->hwndSend : pii->hwndFra
             pci);
     SemEnter();
     /*
      * If we failed to initiate directly with the server, try the frame.
      */
     if (!fRet && WinIsWindow(DMGHAB, pii->hwndSend) &&
             WinIsWindow(DMGHAB, pii->hwndFrame)) {
         SemLeave();
         fRet = SendDDEInit(hwnd, pii->hwndFrame, pci);
         if (fRet) {
             /*
              * OK, client is locked in so he wont go away on a terminate
              * from a random window. If the new server is not the same
              * window as the origonal, send it a terminate so it can
              * go away nicely.
              */
             if (pii->hwndSend != pci->ci.hwndPartner)
                 WinSendMsg(pii->hwndSend, WM_DDE_TERMINATE, 0L, 0L);
         }
         SemEnter();

     }
     if (!fRet)
         pci->ci.xad.state = CONVST_NULL;
     else {
         /*
          * successful initiate means we want to keep these around awhile.
          * removed at window closing time.
          */
         IncHszCount(pci->ci.hszServerApp);
         IncHszCount(pci->ci.hszTopic);
     }
     SemLeave();
     return(fRet);
 }



 /***************************** Private Function ****************************\
 * Allocates and sends a WM_DDE_INITIATE message to hwndTo.  Any failures
 * cause FALSE to be returned.  If hwndTo is NULL, performs equivalent of
 * WinDdeInitiate2().
 *
 * History:
 *   created     12/22/88        sanfords
 *   2/2/89  sanfords    added SEG_GETABLE during monitoring.
 \***************************************************************************/
 BOOL SendDDEInit(hwndFrom, hwndTo, pci)
 HWND hwndFrom;
 HWND hwndTo;
 PCLIENTINFO pci;
 {
     PID pidTo;
     TID tid;
     SEL sel;
     PDDEINIT pddeinit;
     HENUM henum;
     ULONG ul;
     USHORT cchApp, cchTopic;
     PSZ pszApp, pszTopic;
     BOOL fEnumerating;  /* set if extra acks are ok */

     SemCheckOut();

     if (hwndTo == NULL) {
         /*
          * Call on self for all top level frame windows until we are connecte
          * (if enumerating, do em all anyway.)
          */
         fEnumerating = WinQueryWindow(hwndFrom, QW_PARENT, FALSE) !=
                 pci->ci.pai->hwndDmg;
         if (henum = WinBeginEnumWindows(HWND_DESKTOP)) {
             while ((hwndTo = WinGetNextWindow(henum)) &&
                     (fEnumerating || pci->ci.xad.state == CONVST_INIT1)) {
                 if (hwndTo != pci->ci.pai->hwndFrame &&
                         (ul = (ULONG)WinSendMsg(hwndTo, WM_QUERYFRAMEINFO, 0L
                         (ul & FI_FRAME))
                     SendDDEInit(hwndFrom, hwndTo, pci);
             }
             WinEndEnumWindows(henum);
         }
         return(TRUE);
     }

     if (WinQueryWindowProcess(hwndTo, &pidTo, &tid) == NULL)
         return(FALSE);

     SemEnter();
     pszApp = pszFromHsz(pci->ci.hszServerApp, &cchApp);
     pszTopic = pszFromHsz(pci->ci.hszTopic, &cchTopic);
     if (DosAllocSeg(sizeof(DDEINIT) + sizeof(CONVCONTEXT) + cchApp + cchTopic
                 &sel, SEG_GIVEABLE) != 0) {
         SemLeave();
         return(FALSE);
     }
     pddeinit = MAKEP(sel, 0);
     pddeinit->cb = sizeof(DDEINIT);
     pddeinit->offConvContext = sizeof(DDEINIT);
     pddeinit->pszAppName = (PSZ)pddeinit + sizeof(DDEINIT) + sizeof(CONVCONTE
     pddeinit->pszTopic = pddeinit->pszAppName + cchApp;
     CopyBlock((PBYTE)&pci->ci.cc, (PBYTE)DDEI_PCONVCONTEXT(pddeinit), sizeof(
     CopyBlock((PBYTE)pszApp, (PBYTE)pddeinit->pszAppName, cchApp);
     CopyBlock((PBYTE)pszTopic, (PBYTE)pddeinit->pszTopic, cchTopic);
     FarFreeMem(hheapDmg, pszApp, cchApp);
     FarFreeMem(hheapDmg, pszTopic, cchTopic);
     SemLeave();

     if (DosGiveSeg(sel, pidTo, &sel) != 0) {
         DosFreeSeg(sel);
         return(FALSE);
     }

     WinSendMsg(hwndTo, WM_DDE_INITIATE, (MPARAM)hwndFrom, pddeinit);
     if (pidTo != pci->ci.pai->pid)
         DosFreeSeg(sel);
     return(TRUE);
 }



 /***************************** Private Function ****************************\
 *
 *  Alocates and fills in a MYDDES. if pai == 0, the MYDDES is considered
 * unowned.
 *
 * History:  created     1/4/89  sanfords
 * 10/18/89  sanfords Added hack so that if usFmt==DDEFMT_TEXT and hszItem==0L
 *                    the data and item strings are one.
 *                    (This allows for excel EXEC compatibility)
 *   2/2/89  sanfords Added GETABLE during monitoring.
 *  6/13/90  sanfords Altered to not expand hszItem at this point.
 \***************************************************************************/
 PDDESTRUCT AllocDDESel(fsStatus, usFmt, hszItem, cbData, pai)
 USHORT fsStatus;
 USHORT usFmt;
 HSZ hszItem;
 ULONG cbData;
 PAPPINFO pai;
 {
     PMYDDES pmyddes = NULL;
     ULONG cbTotal;
     ULONG cchItem;
     SEL sel;

     SemEnter();
     cchItem = DdeGetHszString(hszItem, NULL, 0L) + 1L;

     /*
      * This hack makes execs conform to EXCELs way.
      */
     if (!hszItem && usFmt == DDEFMT_TEXT)
         cchItem = 0L;

     cbTotal = sizeof(MYDDES) + cchItem + cbData + 1;
     if (cbTotal <= 0xFFFF) {
         if (DosAllocSeg((USHORT)cbTotal, &sel, SEG_GIVEABLE) != 0)
             goto allocDdeExit;
     } else {
         if (DosAllocHuge((USHORT)(cbTotal >> 16), (USHORT)cbTotal, &sel,
                 0, SEG_GIVEABLE) != 0)
             goto allocDdeExit;
     }

     pmyddes = MAKEP(sel, 0);
     pmyddes->cbData = cbData;
     pmyddes->fsStatus = fsStatus;
     pmyddes->usFormat = usFmt;
     pmyddes->offszItemName = sizeof(MYDDES);
     pmyddes->offabData = sizeof(MYDDES) + (USHORT)cchItem;
     pmyddes->ulRes1 = 0L;
     pmyddes->magic = MYDDESMAGIC;
     pmyddes->hszItem = hszItem;
     pmyddes->pai = pai;
     pmyddes->fs = 0;
     *DDES_PABDATA((PDDESTRUCT)pmyddes) = '\0'; /* in case data is never place
     *DDES_PSZITEMNAME((PDDESTRUCT)pmyddes) = '\0';  /* we expand this at post

 allocDdeExit:
     SemLeave();
     return((PDDESTRUCT)pmyddes);
 }




 /***************************** Private Function ****************************\
 * This routine returns the hwnd of a newly created and connected DDE
 * client or NULL if failure.
 *
 * History:  created     1/6/89  sanfords
 \***************************************************************************/
 HCONV GetDDEClientWindow(hConvList, hwndFrame, hwndSend, hszApp, hszTopic, pC
 HCONVLIST hConvList;
 HWND hwndFrame;
 HWND hwndSend;
 HSZ hszApp;
 HSZ hszTopic;
 PCONVCONTEXT pCC;
 {
     HCONV hConv;
     INITINFO ii;
     CONVCONTEXT cc;

     SemCheckOut();

     hConv = WinCreateWindow(hConvList, SZCLIENTCLASS, "", 0L,
             0, 0, 0, 0, (HWND)NULL, HWND_TOP, WID_CLIENT, 0L, 0L);

     if (hConv == NULL)
         return(NULL);

     ii.hszTopic = hszTopic;
     ii.hszAppName = hszApp;
     ii.hwndSend = hwndSend;
     ii.hwndFrame = hwndFrame;
     if (pCC == NULL) {
         pCC = &cc;
         cc.cb = sizeof(CONVCONTEXT);
         cc.fsContext = 0;
         /*##LATER - may want to use process codepage instead */
         cc.usCodepage = syscc.codepage;
         cc.idCountry = syscc.country;
     }
     if (pCC->usCodepage == 0)
         pCC->usCodepage = syscc.codepage;
     if (pCC->idCountry == 0)
         pCC->idCountry = syscc.country;

     ii.pCC = pCC;
     WinSendMsg(hConv, UMCL_INITIATE, (MPARAM)&ii, 0L);

     if (!((USHORT)WinSendMsg(hConv, UM_QUERY, (MPARAM)Q_STATUS, 0L) &
             ST_CONNECTED)) {
         WinDestroyWindow(hConv);
         return(NULL);
     }
     return(hConv);
 }



 /***************************** Private Function ****************************\
 * This routine institutes a callback directly if psi->fEnableCB is set
 * and calls QReply to complete the transaction,
 * otherwise it places the data into the queue for processing.
 *
 * Since hDmgData may be freed by the app at any time once the callback is
 * issued, we cannot depend on it being there for QReply.  Therefore we
 * save all the pertinant data in the queue along with it.
 *
 * Returns fSuccess.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 BOOL MakeCallback(pai, hConv, hszTopic, hszItem, usFmt, usType, hDmgData,
     msg, fsStatus, hConvClient)
 PAPPINFO pai;
 HCONV hConv;
 HSZ hszTopic;
 HSZ hszItem;
 USHORT usFmt;
 USHORT usType;
 HDMGDATA hDmgData;
 USHORT msg;
 USHORT fsStatus;
 HCONV hConvClient;
 {
     PCBLI pcbli;

     SemEnter();

     if (!(pcbli = (PCBLI)NewLstItem(pai->plstCB, ILST_LAST))) {
         pai->LastError = DMGERR_MEMORY_ERROR;
         SemLeave();
         return(FALSE);
     }
     pcbli->hConv = hConv;
     pcbli->hszTopic = hszTopic;
     pcbli->hszItem = hszItem;
     pcbli->usFmt = usFmt;
     pcbli->usType = usType;
     pcbli->hDmgData = hDmgData;
     pcbli->msg = msg;
     pcbli->fsStatus = fsStatus;
     pcbli->hConvPartner = hConvClient;

     if (pai->fEnableCB && !pai->hwndTimer) {
         SemLeave();
         WinPostMsg(pai->hwndDmg, UM_CHECKCBQ, (MPARAM)pai, 0L);
     } else
         SemLeave();

     return(TRUE);
 }



 /*************************************************************************\
 * Attempts to post a DDE message to hwndTo.  Properly frees up pmyddes
 * if afCmd has MDPM_FREEHDATA set.  We do not add pmyddes to the target
 * thread list since we assume that will be done at the receiving end
 * if necessary.
 *
 * Returns fSuccess.
 *
 * 6/12/90 sanfords  Created
 * 6/13/90 sanfords  Made it convert hszItem to a string at this point
 *                   only if hwndTo is not a local guy.
 \*************************************************************************/
 BOOL MyDdePostMsg(
 HWND hwndTo,
 HWND hwndFrom,
 USHORT msg,
 PMYDDES pmyddes,
 PAPPINFO paiFrom,
 USHORT afCmd)
 {
     PID pid;
     TID tid;
     SEL selR;
     BOOL fRet;
     PFNWP pfnwpTo;

     if (!WinQueryWindowProcess(hwndTo, &pid, &tid))
         return FALSE;

     pfnwpTo = (PFNWP)WinQueryWindowPtr(hwndTo, QWP_PFNWP);
     if (cMonitor || (pfnwpTo != ServerWndProc && pfnwpTo != ClientWndProc)) {
         /*
          * its not local - expand hszItem if necessary - always
          * expand if a monitor is installed.
          */
         if (CheckSel(SELECTOROF(pmyddes)) >= sizeof(MYDDES) &&
                 pmyddes->magic == MYDDESMAGIC &&
                 pmyddes->hszItem &&
                 !(pmyddes->fs & HDATA_PSZITEMSET)) {
             pmyddes->fs |= HDATA_PSZITEMSET;
             QueryHszName(pmyddes->hszItem, DDES_PSZITEMNAME(pmyddes),
                     pmyddes->offabData - pmyddes->offszItemName);
         }
     }
     /*
      * Don't try to share seg with ourselves.
      */
     if (paiFrom->pid != pid) {
         selR = SELECTOROF(pmyddes);
         if (DosGiveSeg(SELECTOROF(pmyddes), pid, &selR))
             return FALSE;
         if (afCmd & MDPM_FREEHDATA)
             FreeData(pmyddes, paiFrom);
     } else {
         /*
          * just remove hData from our thread list
          */
         if (afCmd & MDPM_FREEHDATA && !(pmyddes->fs & HDATA_APPOWNED))
             FindPileItem(paiFrom->pHDataPile, CmpULONG, (PBYTE)&pmyddes,
                     FPI_DELETE);
     }
     fRet = (BOOL)WinPostMsg(hwndTo, msg, (MPARAM)hwndFrom, (MPARAM)pmyddes);
     if (!fRet) {
         /*
          * make sure this is freed if it is supposed to be - this covers
          * the case where the target is of the same process and only
          * these two threads are registered.
          */

         tid = paiFrom->tid;
         do {
             if (FindPileItem(paiFrom->pHDataPile, CmpULONG, (PBYTE)&pmyddes,
                     FPI_COUNT))
                 return(FALSE);  /* there is another thread that has this */
             paiFrom = paiFrom->nextThread;
         } while (paiFrom->tid != tid);
         DosFreeSeg(SELECTOROF(pmyddes));
     }
     return(fRet);
 }


 DMGHSZ.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGHSZ.C

 /****************************** Module Header ******************************\
 * Module Name: DMGLATOM.C
 *
 * This module contains functions used for HSZ control.
 *
 * Created:  8/2/88    sanfords
 * Added case preservation/insensitive   1/22/90       Sanfords
 * 6/12/90 sanfords  Fixed HSZ local string allocation size errors.
 *                   Added latom validation checks
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddemlp.h"


 /*
  * since the top 12 bits of any latom is always 0 (unless we have > 16
  * atom tables!) we can encode any ulong into only 5 bytes.
  */
 #define ENCODEBYTES         5
 #define MAX_LATOMSTRSIZE    255 - ENCODEBYTES - 2
 char szT[MAX_LATOMSTRSIZE + 1 + ENCODEBYTES];  /* used for HSZ expansion */

 extern BOOL APIENTRY WinSetAtomTableOwner( HATOMTBL, PID );


 /*********************** LATOM management functions *************************
 * An HSZ is a long atom which holds an encoded reference to two other long
 * atoms.  One long atom is for the actual string, the other is for its
 * uppercase version.  Two HSZs are ranked by their uppercase latoms.
 * This makes HSZs case insensitive, case preserving.  An latom
 * is an atom with an atom table index tacked onto the HIUSHORT part of
 * of the latom.  Strings too long for the atom manager are split up and
 * each piece is prepended with a coded string that represents the
 * LATOM of the rest of the string.  LATOM strings thus may be of any length.
 * (up to 64K in this version)
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/

 /***************************** Private Function ****************************\
 * Allocates space in the DDE manager heap for a string queried from the DDE
 * manager latomtable.  The case sensitive string is returned.
 *
 * This function should be serialized.  Memory allocation or latom table failu
 * results in a 0 return value.
 *
 * 0 latoms result in a NULL terminated empty string.
 *
 * Note that *pcch is set to the length of the string INCLUDING the null
 * terminator.  This way a wild string has a cch=1.
 *
 * History:
 *   created     12/22/88        sanfords
 *   11/10/89    sanfords        modified to return '\0' on invalid atom.
 \***************************************************************************/
 PSZ pszFromHsz(hsz, pcch)
 HSZ hsz;
 USHORT FAR *pcch;
 {
     PSZ psz;
     LATOM latom;
     char sz[ENCODEBYTES + 1];
     register USHORT cch;

     SemCheckIn();

     if (hsz == 0)
         cch = 1;
     else {
         QuerylatomName((LATOM)hsz, sz, ENCODEBYTES + 1);
         latom = Decode(sz);     /* take origonal case version */
         cch = QuerylatomLength(latom) + 1;
     }

     psz = (PSZ)FarAllocMem(hheapDmg, cch);
     if (psz == 0) {
         *pcch = '\000';
         return(0);
     }

     if (hsz == 0) {
         *pcch = 1;
         *psz = '\0';
     } else {
         *pcch = cch;
         if (QuerylatomName(latom, psz, cch) == 0) {
             AssertF(FALSE, "pszFromHsz - bad latom");
             *psz = '\0';        /* invalid case - never expected */
         }
     }
     return(psz);
 }


 /***************************** Private Function ****************************\
 * HSZ GetHsz(psz, cc, cp, fAdd)
 * PSZ psz;
 * USHORT cc;
 * USHORT cp;
 * BOOL fAdd;
 *
 * The goal of this routine is to convert a psz to an hsz.  This uses the
 * atom manager for its dirty work.  This call has the side effect of
 * incrementing the use count for the hsz returned and its associated latoms
 * if fAdd is set.
 *
 * if fAdd is FALSE, NULL is returned if the hsz doesn't exist.
 *
 * History:
 *   created     12/23/88    sanfords
 \***************************************************************************/
 HSZ GetHsz(psz, cc, cp, fAdd)
 PSZ psz;
 USHORT cc;
 USHORT cp;
 BOOL fAdd;
 {
     LATOM latom1, latom2;
     USHORT cb;
     PSZ pszT;
     BOOL fNew = FALSE;
     HSZ hsz;

     /*
      * NULL or 0 length pszs are considered wild HSZs.
      */
     if (psz == NULL || *psz == '\0')
         return(0L);

     SemEnter();

     if (!(latom1 = FindAddlatom(psz, fAdd))) {
         AssertF(!fAdd, "GetHsz - Atom Add failed");
         SemLeave();
         return(0L);
     }

     cb = lstrlen(psz) + 1;

     if (!(pszT = FarAllocMem(hheapDmg, max(cb, ENCODEBYTES * 2 + 1)))) {
         SemLeave();
         return(0L);
     }

     CopyBlock((PBYTE)psz, (PBYTE)pszT, cb);
     WinUpper(DMGHAB, cp ? cp : syscc.codepage, cc ? cc : syscc.country, pszT)
     latom2 = FindAddlatom(pszT, fAdd);

     if (!latom2) {
         AssertF(!fAdd, "GetHsz - Atom Add(2) failed");
         hsz = 0;
     } else {
         *Encode(latom2, Encode(latom1, pszT)) = '\000';
         hsz = (HSZ)FindAddlatom(pszT, fAdd);
     }
     FarFreeMem(hheapDmg, pszT, max(cb, ENCODEBYTES * 2 + 1));
     SemLeave();
     return(hsz);
 }



 /*
  * Note that all three associated latoms are freed.
  */
 BOOL FreeHsz(hsz)
 HSZ hsz;
 {
     char sz[ENCODEBYTES * 2 + 1];

     SemEnter();
     if (hsz && QuerylatomName((LATOM)hsz, sz, ENCODEBYTES * 2 + 1)) {
         Freelatom(Decode((PBYTE)sz));
         Freelatom(Decode((PBYTE)&sz[ENCODEBYTES]));
         Freelatom((LATOM)hsz);
     }
     SemLeave();
     return(TRUE);
 }



 /*
  * Note that all three associated latoms are incremented.
  */
 BOOL IncHszCount(hsz)
 HSZ hsz;
 {
     char sz[ENCODEBYTES * 2 + 1];
     register BOOL fRet;

     if (hsz == 0)
         return(TRUE);

     SemEnter();

     QuerylatomName((LATOM)hsz, sz, ENCODEBYTES * 2 + 1);
     fRet = InclatomCount(Decode((PBYTE)sz)) &&
                 InclatomCount(Decode((PBYTE)&sz[ENCODEBYTES])) &&
                 InclatomCount((LATOM)hsz);
     SemLeave();
     return(fRet);
 }



 /***************************** Private Function ****************************\
 * This routine adds an atom table and returns its handle.  Returns fSuccess.
 *
 * Effects cAtbls, aAtbls, iAtblCurrent;
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 BOOL AddAtomTable(fInit)
 BOOL fInit;
 {
     PHATOMTBL pat;

     SemEnter();

     if (!(pat = (PHATOMTBL)FarAllocMem(hheapDmg,
             sizeof(HATOMTBL) * (cAtbls + 1)))) {
         SemLeave();
         return(FALSE);
     }

     if (!fInit) {
         CopyBlock((PBYTE)aAtbls, (PBYTE)pat, sizeof(HATOMTBL) * cAtbls);
         FarFreeMem(hheapDmg, aAtbls, sizeof(HATOMTBL) * cAtbls);
     }

     aAtbls = pat;

     if (!(aAtbls[cAtbls] = WinCreateAtomTable(0, 0)))
         return(FALSE);
     /*
      * Share our atom tables with all processes...
      */
     if (!WinSetAtomTableOwner(aAtbls[cAtbls], NULL)) {
         AssertF(FALSE, "AddAtomTable - WinSetAtomTable failed");
         return(FALSE);
     }
     iAtblCurrent = cAtbls++;
     SemLeave();
     return(TRUE);
 }



 USHORT QueryHszLength(hsz)
 HSZ hsz;
 {
     char sz[ENCODEBYTES + 1];
     USHORT us;

     if (!hsz)
         return(0);
     SemEnter();
     QuerylatomName((LATOM)hsz, sz, ENCODEBYTES + 1);
     us = QuerylatomLength(Decode(sz));
     SemLeave();
     return(us);
 }



 USHORT QueryHszName(hsz, psz, cchMax)
 HSZ hsz;
 PSZ psz;
 USHORT cchMax;
 {
     char sz[ENCODEBYTES + 1];
     register USHORT usRet;

     if (hsz == 0) {
         if (psz)
             *psz = '\000';
         return(1);
     } else {
         usRet = 0;
         SemEnter();
         if (QuerylatomName((LATOM)hsz, sz, ENCODEBYTES + 1))
             usRet = QuerylatomName(Decode(sz), psz, cchMax);
         SemLeave();
         return(usRet);
     }
 }



 /*
  * returns 0 if ==, -1 if hsz1 < hsz2, 1 if hsz1 > hsz2, 2 on error
  */
 SHORT CmpHsz(hsz1, hsz2)
 HSZ hsz1, hsz2;
 {
     char sz[ENCODEBYTES * 2 + 1];
     LATOM latom;
     SHORT usRet;

     if (hsz1 == hsz2)
         return(0);
     if (!hsz1)
         return(-1);
     if (!hsz2)
         return(1);

     usRet = 2;
     SemEnter();
     if (QuerylatomName((LATOM)hsz1, sz, ENCODEBYTES * 2 + 1)) {
         latom = Decode(&sz[ENCODEBYTES]);   /* use UPPERCASE form for compari
         if (QuerylatomName((LATOM)hsz2, sz, ENCODEBYTES * 2 + 1)) {
             latom = latom - Decode(&sz[ENCODEBYTES]);
             usRet = latom == 0 ? 0 : (latom > 0 ? 1 : -1);
         }
     }
     SemLeave();
     return(usRet);
 }




 /***************************** Private Function ****************************\
 * Returns the length of the latom given without NULL terminator.
 * Wild LATOMs have a length of 0.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 USHORT QuerylatomLength(latom)
 LATOM latom;
 {
     USHORT cb;
     USHORT cbT = 0;
     BYTE ab[ENCODEBYTES + 1];

     AssertF(HIUSHORT(latom) < cAtbls, "Invalid latom");
     if (latom == 0)
         return(0);
     SemCheckIn();
     while (TRUE) {
         if (!(cb = WinQueryAtomLength(aAtbls[HIUSHORT(latom)],
                 LOUSHORT(latom)))) {
             AssertF(cbT == 0, "QuerylatomLength - failed on continued latom")
             return(0);
         }

         cbT += cb;

         if (cb <= MAX_LATOMSTRSIZE) {
             return(cbT);
         }

         /*
          * it MUST be a huge latom.
          */
         if (!(WinQueryAtomName(aAtbls[HIUSHORT(latom)], LOUSHORT(latom),
                 (PSZ)ab, ENCODEBYTES + 1))) {
             AssertF(FALSE, "QuerylatomLength - Length but no name");
             return(0);
         }

         latom = Decode(ab);
         cbT -= ENCODEBYTES;
     }
 }



 USHORT QuerylatomName(latom, psz, cchMax)
 LATOM latom;
 PSZ psz;
 USHORT cchMax;
 {
     USHORT cb;
     extern char szT[];

     if (HIUSHORT(latom) >= cAtbls) {
         AssertF(FALSE, "Invalid latom");
         psz[0] = '\0';
         return(0);
     }

     AssertF(latom != 0, "QuerylatomName - 0 latom");
     SemCheckIn();
     cb = WinQueryAtomLength(aAtbls[HIUSHORT(latom)], LOUSHORT(latom));
     if (cb > MAX_LATOMSTRSIZE) {
         if (!WinQueryAtomName(aAtbls[HIUSHORT(latom)], LOUSHORT(latom), szT,
                 MAX_LATOMSTRSIZE + ENCODEBYTES + 1)) {
             AssertF(FALSE, "QuerylatomName - length but no name");
             return(0);
         }
         CopyBlock(szT + ENCODEBYTES, psz, min(MAX_LATOMSTRSIZE, cchMax));
         latom = Decode((PBYTE)szT);
         cb = MAX_LATOMSTRSIZE + QuerylatomName(latom, psz + MAX_LATOMSTRSIZE,
                 cchMax > MAX_LATOMSTRSIZE ? cchMax - MAX_LATOMSTRSIZE : 0);

     } else {
         WinQueryAtomName(aAtbls[HIUSHORT(latom)], LOUSHORT(latom), psz, cchMa
     }
     psz[cchMax - 1] = '\0';     /* add NULL terminator */
     return(min(cb, cchMax - 1));
 }



 /***************************** Private Function ****************************\
 * This uses globals szT, aAtbls, cAtbls, and iAtblCurrent to add or
 * find the latom for psz depending on fAdd.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 LATOM FindAddlatom(psz, fAdd)
 PSZ psz;
 BOOL fAdd;
 {
     LATOM latom;

     AssertF(psz != NULL, "FindAddlatom - NULL psz");
     AssertF(*psz != '\0', "FindAddlatom - NULL psz string");
     SemCheckIn();
     if (lstrlen(psz) > MAX_LATOMSTRSIZE) {
         latom = FindAddlatom(psz + MAX_LATOMSTRSIZE, fAdd);
         CopyBlock((PBYTE)psz, Encode((ULONG)latom, szT),
                 (ULONG)MAX_LATOMSTRSIZE - ENCODEBYTES + 1);
         szT[MAX_LATOMSTRSIZE + ENCODEBYTES] = '\0';
         latom = FindAddlatomHelper(szT, fAdd);
         return(latom);
     } else {
         return(FindAddlatomHelper(psz, fAdd));
     }
 }




 LATOM FindAddlatomHelper(psz, fAdd)
 PSZ psz;
 BOOL fAdd;
 {
     int i;
     ATOM atom;
     ATOM (APIENTRY *lpfn)(HATOMTBL, PSZ);

     SemCheckIn();
     if (fAdd) {
         AssertF(++cAtoms, "Possible atom count overflow");
         lpfn = WinAddAtom;
     } else
         lpfn = WinFindAtom;

     if (!(atom = (*lpfn)(aAtbls[i = iAtblCurrent], psz))) {
         /*
          * Must be full/not found, try all the existing tables
          */
         for (i = 0; i < cAtbls; i++) {
             if (i != iAtblCurrent) {
                 if (atom = (*lpfn)(aAtbls[i], psz)) {
                     if (fAdd)
                         iAtblCurrent = i;
                     break;
                 }
             }
         }

         if (!atom) {
             if (fAdd) {
                 /*
                  * they're all full, make another table.
                  */
                 if (!AddAtomTable(FALSE)) {
                     return(0L);
                 }
                 if (!(atom = (*lpfn)(aAtbls[iAtblCurrent], psz))) {
                     return(0L);
                 }
             } else {
                 return(0L);
             }
         }
     }
     return((LATOM)MAKEP(i, atom));
 }





 BOOL InclatomCount(latom)
 LATOM latom;
 {
     AssertF(HIUSHORT(latom) < cAtbls, "Invalid latom");
     AssertF(latom != 0, "InclatomCount - 0 latom");
     SemCheckIn();
     AssertF(++cAtoms, "Possible atom count overflow");
     return(WinAddAtom(aAtbls[HIUSHORT(latom)], MAKEP(0XFFFF, LOUSHORT(latom))
 }



 BOOL Freelatom(latom)
 LATOM latom;
 {
     AssertF(HIUSHORT(latom) < cAtbls, "Invalid latom");
     AssertF(latom != 0, "Freelatom - 0 latom");
     AssertF(WinQueryAtomUsage(aAtbls[HIUSHORT(latom)], LOUSHORT(latom)),
             "Freelatom - Freeing Non-existing atom");
     SemCheckIn();

     if (WinDeleteAtom(aAtbls[HIUSHORT(latom)], LOUSHORT(latom))) {
         AssertF(FALSE, "Freelatom - WinDeleteAtom failed");
         return(FALSE);
     }
     AssertF(--cAtoms >= 0, "Freelatom - negative atom count");
     return(TRUE);
 }




 #ifdef DEBUG
 BASEVAL '0'     /* more readable for debugging */
 #else
 BASEVAL 1       /* less likely to conflict with a string */
 #endif

 /***************************** Private Function ****************************\
 * Converts an latom into a ENCODEBYTES character string apropriate for
 * atomization. (NULL terminator must be added)
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 PBYTE Encode(latom, pb)
 ULONG latom;
 PBYTE pb;
 {
     int i;


     AssertF(HIUSHORT(latom) < cAtbls, "Invalid latom");
     for (i = 0; i < ENCODEBYTES; i++) {
         *pb++ = ((BYTE)latom & 0x0F) + BASEVAL;
         latom >>= 4;
     }
     return(pb);
 }



 /***************************** Private Function ****************************\
 * This takes a pointer to a buffer of 8 bytes which is a coded LATOM and
 * returns the LATOM.
 *
 * History:
 *   Created     9/12/89    Sanfords
 \***************************************************************************/
 LATOM Decode(pb)
 PBYTE pb;
 {
     ULONG ul = 0;
     int i;

     for (i = ENCODEBYTES - 1; i >= 0; i--) {
         ul <<= 4;
         ul += (ULONG)(pb[i] - BASEVAL);
     }
     return((LATOM)ul);
 }


 /*
  * This routine extracts the hszItem out of an existing hData handle.
  * local conversations can use the hsz directly out of the handle while
  * non-dll conversations will have to generate the hsz from the string.
  */
 HSZ GetHszItem(
 PMYDDES pmyddes,
 PCONVCONTEXT pCC,
 BOOL fAdd)
 {
     if (CheckSel(SELECTOROF(pmyddes)) >= sizeof(MYDDES) &&
             pmyddes->magic == MYDDESMAGIC) {
         if (fAdd)
             IncHszCount(pmyddes->hszItem);
         return(pmyddes->hszItem);
     } else {
         return(GetHsz(DDES_PSZITEMNAME(pmyddes), pCC->idCountry,
                 pCC->usCodepage, fAdd));
     }
 }


 DMGMON.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGMON.C

 /****************************** Module Header ******************************\
 * Module Name: DMGMON.C
 *
 * This module contains functions used for DDE monitor control.
 *
 * Created:  8/2/88    sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddemlp.h"
 #define MSGF_DDEPOSTMSG     3

 #define freeMonStr(psz) MyFreeMem(hheapDmg, (NPBYTE)(USHORT)psz, MAX_MONITORS


 BOOL EXPENTRY DdeSendHookProc(hab, psmh, fInterTask)
 HAB hab;
 PSMHSTRUCT psmh;
 BOOL fInterTask;
 {
     PSZ psz;
     PSZ pszSave;
     PSZ pszLast;

     UNUSED hab;
     UNUSED fInterTask;

     if (psmh->msg == WM_DDE_INITIATE || psmh->msg == WM_DDE_INITIATEACK) {
         if (allocMonStr(&pszSave, &pszLast)) {
             psz = timestamp(pszSave, pszLast);
             psz = lstrcat(psz, " ", pszLast);
             psz = ltoa((ULONG)psmh->mp1, psz, pszLast);
             psz = lstrcat(psz, " -> ", pszLast);
             psz = ltoa((ULONG)psmh->hwnd, psz, pszLast);
             psz = lstrcat(psz, "\n\r", pszLast);
             psz = ddeMsgToPsz(psmh->msg, psz, pszLast);
             psz = pddesToPsz(psmh->msg, (PDDESTRUCT)psmh->mp2, psz, pszLast);
             psz = lstrcat(psz, ")\n\r", pszLast);
             MonitorBroadcast(pszSave);
             freeMonStr(pszSave);
         }
     }
     return(FALSE);
 }




 BOOL EXPENTRY DdePostHookProc(hab, pqmsg, fs)
 HAB hab;
 PQMSG pqmsg;
 USHORT fs;
 {
     PSZ psz;
     PSZ pszSave;
     PSZ pszLast;

     UNUSED hab;

     if (fs && pqmsg->msg >= WM_DDE_FIRST && pqmsg->msg <= WM_DDE_LAST) {
         pszLast = psz + MAX_MONITORSTR;
         if (allocMonStr(&pszSave, &pszLast)) {
             psz = timestamp(pszSave, pszLast);
             psz = lstrcat(psz, " ", pszLast);
             psz = ltoa((ULONG)pqmsg->mp1, psz, pszLast);
             psz = lstrcat(psz, " -> ", pszLast);
             psz = ltoa((ULONG)pqmsg->hwnd, psz, pszLast);
             psz = lstrcat(psz, "\n\r", pszLast);
             psz = ddeMsgToPsz(pqmsg->msg, psz, pszLast);
             psz = pddesToPsz(pqmsg->msg, (PDDESTRUCT)pqmsg->mp2, psz, pszLast
             psz = lstrcat(psz, ")\n\r", pszLast);
             MonitorBroadcast(pszSave);
             freeMonStr(pszSave);
         }
     }
     return(FALSE);
 }

 /*
  * This guy sends a UM_MONITOR to all the monitor windows (up to MAX_MONITOR)
  * The cheap restriction is due to needing to not be in the semaphore
  * while the monitor is in control yet needing to keep access to pai in
  * the semaphore.
  */
 void MonitorBroadcast(psz)
 PSZ psz;
 {
     HWND hwnd[MAX_MONITORS];
     PAPPINFO pai;
     register USHORT i = 0;

     SemCheckOut();
     SemEnter();
     pai = pAppInfoList;
     while (pai && i < cMonitor && i < MAX_MONITORS) {
         if (pai->hwndMonitor) {
             hwnd[i] = pai->hwndMonitor;
             i++;
         }
         pai = pai->next;
     }
     SemLeave();

     for (i = 0; i < cMonitor; i++)
         WinSendMsg(hwnd[i], UM_MONITOR, (MPARAM)psz, 0L);
 }


 /*
  * We need to allocate the string buffer so that recursive calls will work.
  * We also need to do this because the DLL DS is shared between all potential
  * monitor processes.
  *
  * This also initializes the psz for us with a null terminator and checks
  * cMonitor for us.  If this fails, no monitor action is done.
  *
  * ppsz will contain a pointer to the begining of the allocated buffer.
  * ppszLast will contain a pointer to the end of the allocated buffer.
  */
 BOOL allocMonStr(ppsz, ppszLast)
 PSZ far *ppsz;
 PSZ far *ppszLast;
 {
     SemEnter();
     if (cMonitor == 0 ||
             ((*ppsz = FarAllocMem(hheapDmg, MAX_MONITORSTR + 1)) == NULL)) {
         SemLeave();
         return(FALSE);
     }
     *ppszLast = *ppsz + MAX_MONITORSTR;
     **ppsz = '\0';
     SemLeave();
     return(TRUE);
 }



 MRESULT EXPENTRY MonitorWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 register MPARAM mp1;
 MPARAM mp2;
 {
     PAPPINFO pai;
     HDMGDATA hDmgData;

     pai = GetCurrentAppInfo(FALSE);

     switch (msg) {
     case WM_CREATE:
         mp1  = (PSZ)"\n\rMonitor Created\n\r\n\r";
         goto MonOut;
         break;

     case WM_DESTROY:
         mp1 = (PSZ)"\n\r\n\rMonitor Destroyed\n\r";
         /* fall through */

     case UM_MONITOR:
         /*
          * mp1 = psz to print
          */
 MonOut:
         hDmgData = DdePutData((PSZ)mp1, (ULONG)(lstrlen(mp1) + 1),
                 0L, (HSZ)0L, DDEFMT_TEXT, 0);
         pai->cInCallback++;
         DoCallback(pai, 0, 0, 0, DDEFMT_TEXT, XTYP_MONITOR, hDmgData);
         if (pai->cInCallback > 0)   /* test incase exitlist processing messed
             pai->cInCallback--;
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }

     return(0);
 }




 DMGQ.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGQ.C

 /****************************** Module Header ******************************\
 * Module Name: DMGQ.C
 *
 * DDE Manager queue control functions.
 *
 * Created: 9/1/89 Sanford Staab
 *
 * This is a general queue manager - yes another one!
 * Queues are each allocated within their own segment and have a
 * QST structure associated with that heap.  Each queue item
 * is allocated within the heap segment.  The offset of the items
 * address combined with an instance count is used as the item ID.
 * This is both unique and allows for instant location of an item.
 * New items are added to the head of the queue which is a doubly linked
 * list.  The next links point to more recent entries, the prev pointers
 * to older entries.  The next of the head is the tail.  The prev of the
 * tail is the head.  All pointers are far.
 * Queue Data may be of any structure type that begins identical to
 * a QUEUEITEM structure.  Functions that require an cbItem perameter
 * should be given the size of the specialized queue item structure.
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #include "ddemlp.h"


 /***************************** Private Function ****************************\
 *
 * Creates a Queue for items of cbItem.
 * Returns NULL on error.
 *
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 PQST CreateQ(cbItem)
 USHORT cbItem;
 {
     QST cq;
     PQST pQ;

     cq.cItems = 0;
     cq.instLast = 0;
     cq.cbItem = cbItem;
     cq.pqiHead = NULL;
     if (!(cq.hheap = MyCreateHeap(0, sizeof(QST) + cbItem << 3,
             cbItem << 3, cbItem, cbItem, HEAPFLAGS)))
         return(NULL);
     if (!(pQ = (PQST)FarAllocMem(cq.hheap, sizeof(QST)))) {
         MyDestroyHeap(cq.hheap);
         return(0);
     }
     *pQ = cq;
     return(pQ);
 }



 /***************************** Private Function ****************************\
 *
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 BOOL DestroyQ(pQ)
 PQST pQ;
 {
     if (pQ)
         MyDestroyHeap(pQ->hheap);
     return(TRUE);
 }



 /***************************** Private Function ****************************\
 *
 * returns a long pointer to the queue item data created.  The new item
 * is added to the head of the queue.  The queue's cbItem specified at
 * creation is used for allocation.
 *
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 PQUEUEITEM Addqi(pQ)
 PQST pQ;
 {
     PQUEUEITEM pqi;

     if ((pqi = (PQUEUEITEM)FarAllocMem(pQ->hheap, pQ->cbItem)) == NULL) {
         if (pQ->cItems == 0)
             return(0);
         /*
          * remove the oldest item to make room for the new.
          */
         pqi = pQ->pqiHead->next;
         SemEnter();
         pqi->prev->next = pqi->next;
         pqi->next->prev = pqi->prev;
         SemLeave();
     }

     SemEnter();
     if (pQ->cItems == 0) {
         pQ->pqiHead = pqi->prev = pqi->next = pqi;
     } else {
         pqi->prev = pQ->pqiHead;
         pqi->next = pQ->pqiHead->next;
         pQ->pqiHead->next->prev = pqi;
         pQ->pqiHead->next = pqi;
         pQ->pqiHead = pqi;
     }
     SemLeave();
     pQ->cItems++;
     pqi->inst = ++pQ->instLast;
     return(pqi);
 }




 /***************************** Private Function ****************************\
 *
 *  The id given is an external LONG id, not an item instance number.
 *  If id is QID_NEWEST, the head item is deleted.
 *  If id is QID_OLDEST, the tail item is deleted.
 *
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 void Deleteqi(pQ, id)
 PQST pQ;
 ULONG id;
 {
     PQUEUEITEM pqi;

     SemEnter();
     pqi = Findqi(pQ, id);
     if (pqi == NULL) {
         SemLeave();
         return;
     }
     pqi->prev->next = pqi->next;
     pqi->next->prev = pqi->prev;
     if (pqi == pQ->pqiHead)
         pQ->pqiHead = pqi->prev;
     if (!(--pQ->cItems))
         pQ->pqiHead = NULL;
     FarFreeMem(pQ->hheap, pqi, pQ->cbItem);
     SemLeave();
 }






 /***************************** Private Function ****************************\
 *
 *  The id given is an external LONG id, not an item instance number.
 *
 *  if id == QID_NEWEST, returns the head queue data item.
 *  if id == QID_OLDEST, returns the tail queue data item.
 *  if the id is not found or the queue is empty, NULL is returned.
 *  if found, pqi is returned.
 *
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 PQUEUEITEM Findqi(pQ, id)
 PQST pQ;
 ULONG id;
 {
     PQUEUEITEM pqi;

     SemCheckIn();
     if (pQ == NULL || pQ->pqiHead == NULL)
         return(NULL);

     if (id == QID_OLDEST)
         return(pQ->pqiHead->next);

     if (id == QID_NEWEST)
         return(pQ->pqiHead);

     if (id) {
         pqi = PFROMID(pQ, id);
         if (pqi->inst == HIUSHORT(id)) {
             return(pqi);
         }
         return(NULL);
     }
 }


 DMGSTR.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGSTR.C

 /****************************** Module Header ******************************\
 * Module Name: DMGSTR.C
 *
 * DDE manager string handling routines
 *
 * Created: 1/31/88 Sanford Staab
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "ddemlp.h"
 #include "ctype.h"



 /***************************** Private Function ****************************\
 *
 * returns string length not counting null terminator.
 *
 * History:  1/1/89  created     sanfords
 \***************************************************************************/
 int lstrlen(psz)
 PSZ psz;
 {
     int c = 0;

     while (*psz != 0) {
         psz++;
         c++;
     }
     return(c);
 }

 /***************************** Public  Function ****************************\
 * Concatonates psz1 and psz2 into psz1.
 * returns psz pointing to end of concatonated string.
 * pszLast marks point at which copying must stop.  This makes this operation
 * safe for limited buffer sizes.
 *
 * History:  1/1/89  created sanfords
 \***************************************************************************/
 PSZ lstrcat(psz1, psz2, pszLast)
 PSZ psz1, psz2, pszLast;
 {
     psz1 += lstrlen(psz1);
     while (*psz2 != '\0' && psz1 < pszLast) {
         *psz1++ = *psz2++;
     }
     *psz1 = '\0';
     return(psz1);
 }

 /***************************** Private Function ****************************\
 * DESCRIPTION: ASCII dependent converter of DDE structure data to a string.
 * returns psz pointing to end of copy.
 * During monitoring we allocate segments as gettable so we can monitor them.
 *
 * History:      Created 1/31/89         sanfords
 \***************************************************************************/
 PSZ pddesToPsz(msg, pddes, psz, pszLast)
 USHORT msg;
 PDDESTRUCT pddes;
 PSZ psz;
 PSZ pszLast;
 {
     USHORT cb;
     PBYTE pData;
 #define pDdeInit ((PDDEINIT)pddes)

     *psz = '\0';
     switch (msg) {
     case WM_DDE_REQUEST:
     case WM_DDE_ACK:
     case WM_DDE_DATA:
     case WM_DDE_ADVISE:
     case WM_DDE_UNADVISE:
     case WM_DDE_POKE:
     case WM_DDE_EXECUTE:
         psz = lstrcat(psz, "S:", pszLast);
         psz = Status(pddes->fsStatus, psz, pszLast);
         psz = lstrcat(psz, " F:", pszLast);
         psz = Format(pddes->usFormat, psz, pszLast);
         psz = lstrcat(psz, " I:", pszLast);
         psz = lstrcat(psz, DDES_PSZITEMNAME(pddes), pszLast);
         if (pddes->cbData)
             psz = lstrcat(psz, "\n\r  Data:", pszLast);
         pData = DDES_PABDATA(pddes);

         for (cb = 0; (ULONG)cb < pddes->cbData && psz < pszLast; cb++, pData+
             /*
              * new line every 64 chars
              */
             if ((cb & 0x3F) == 0) {
                 *psz = '\0';
                 psz = lstrcat(psz, "\n\r    ", pszLast);
             }
             if (*pData > 0x20)
                 *psz = *pData;
             else
                 *psz = '.';

             *psz++ = *psz & 0x7f;
         }
         CopyBlock("\n\r", pszLast - 3, 3L);
         break;

     case WM_DDE_INITIATEACK:
     case WM_DDE_INITIATE:
         if (CheckSel(SELECTOROF(pDdeInit))) {
             psz = lstrcat(psz, "A:", pszLast);
             psz = lstrcat(psz, pDdeInit->pszAppName, pszLast);
             psz = lstrcat(psz, " T:", pszLast);
             psz = lstrcat(psz, pDdeInit->pszTopic, pszLast);
         }
         break;

     case WM_DDE_TERMINATE:
         break;
     }
     *psz = '\0';
     return(psz);

 #undef pDdeInit

 }


 PSZ Status(fs, psz, pszLast)
 USHORT fs;
 PSZ psz;
 PSZ pszLast;
 {
     if (fs & DDE_FACK) {
         psz = lstrcat(psz, "ACK ", pszLast);
     }
     if (fs & DDE_FBUSY) {
         psz = lstrcat(psz, "BUSY ", pszLast);
     }
     if (fs & DDE_FNODATA) {
         psz = lstrcat(psz, "NODATA ", pszLast);
     }
     if (fs & DDE_FACKREQ) {
         psz = lstrcat(psz, "ACKREQ ", pszLast);
     }
     if (fs & DDE_FRESPONSE) {
         psz = lstrcat(psz, "RESPONSE ", pszLast);
     }
     if (fs & DDE_NOTPROCESSED) {
         psz = lstrcat(psz, "NOTPROCESSED ", pszLast);
     }
     if (fs & DDE_FAPPSTATUS) {
         psz = lstrcat(psz, "APPSTAT=", pszLast);
         psz = itoa(fs & DDE_FAPPSTATUS, psz, pszLast);
         *psz++ = ' ';
         *psz++ = '\0';
     }
     if (fs & DDE_FRESERVED) {
         psz = lstrcat(psz, "RESERVED=", pszLast);
         psz = itoa(fs & DDE_FRESERVED, psz, pszLast);
     }
     return(psz);
 }


 PSZ Format(fmt, psz, pszLast)
 USHORT fmt;
 PSZ psz;
 PSZ pszLast;
 {
     if (fmt > 0xbfff) {
         *psz++ = '"';
         psz += WinQueryAtomName(WinQuerySystemAtomTable(), fmt, psz, pszLast
         *psz++ = '"';
         *psz = '\0';
     } else if (fmt == DDEFMT_TEXT) {
         psz = lstrcat(psz, "TEXT", pszLast);
     } else {
         psz = itoa(fmt, psz, pszLast);
     }
     return(psz);
 }




 /***************************** Private Function ****************************\
 * DESCRIPTION: puts an apropriate string for a DDE message into psz. pszLast
 * specifies the last spot to copy.  Returns a psz pointing to the end of
 * the copyed data.
 *
 * History:      Created 1/31/89         sanfords
 \***************************************************************************/
 PSZ ddeMsgToPsz(msg, psz, pszLast)
 USHORT msg;
 PSZ psz;
 PSZ pszLast;
 {
     psz = lstrcat(psz, " ", pszLast);
     if (msg < WM_DDE_FIRST || msg > WM_DDE_LAST) {
         psz = itoa(msg, psz, pszLast);
     } else {
         WinLoadString(DMGHAB, hmodDmg, msg, pszLast - psz + 1, psz);
         psz += lstrlen(psz);
     }
     return(lstrcat(psz, "(", pszLast));
 }

 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   fills psz with a hex string "0xdddd" and returns psz pointing to the 0
 *   terminator at the end.  copying will never go beyond pszLast.
 *
 * History:      Created 1/31/89        sanfords
 \***************************************************************************/
 PSZ itoa(us, psz, pszLast)
 USHORT us;
 PSZ psz;
 PSZ pszLast;
 {
     if (psz > pszLast - 7)
         return(psz);
     *psz++ = '0';
     *psz++ = 'x';
     return(stoa(psz, us));
 }

 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   fills psz with a hex string "0xdddddddd" and returns psz pointing to the
 *   terminator at the end.  copying will never go beyond pszLast.
 *
 * History:      Created 1/31/89        sanfords
 \***************************************************************************/
 PSZ ltoa(ul, psz, pszLast)
 ULONG ul;
 PSZ psz;
 PSZ pszLast;
 {
     if (psz > pszLast - 11)
         return(psz);
     *psz++ = '0';
     *psz++ = 'x';
     psz = stoa(psz, HIUSHORT(ul));
     return(stoa(psz, LOUSHORT(ul)));
 }


 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   fills psz with a hex string "dddd" and returns psz pointing to the 0
 *   terminator at the end.
 *
 * History:      Created 1/31/89        sanfords
 \***************************************************************************/
 PSZ stoa(psz, us)
 PSZ psz;
 USHORT us;
 {
     static char dtoa[] = "0123456789abcdef";

     *psz++ = dtoa[(us & 0xf000) >> 12];
     *psz++ = dtoa[(us & 0xf00) >> 8];
     *psz++ = dtoa[(us & 0xf0) >> 4];
     *psz++ = dtoa[us & 0xf];
     *psz = '\0';
     return(psz);
 }


 /*
  * Decimal to ascii
  */
 PSZ dtoa(psz, us, fRecurse)
 PSZ psz;
 USHORT us;
 BOOL fRecurse;
 {
     if (us > 9) {
         psz = dtoa(psz, us / 10, TRUE);
         *psz++ = (UCHAR)(us % 10) + '0';
     } else if (us > 0)
         *psz++ = (UCHAR)us + '0';
     else if (!fRecurse)
         *psz++ = '0';
     *psz = '\000';
     return(psz);
 }


 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   fills psz with a hex time stamp and returns psz pointing to the 0
 *   terminator at the end.
 *
 * History:      Created 5/9/89        sanfords
 \***************************************************************************/
 PSZ timestamp(psz, pszLast)
 PSZ psz;
 PSZ pszLast;
 {
     DATETIME dt;
     static USHORT prevTime = 0;
     USHORT Time;

     DosGetDateTime(&dt);
     Time = MAKESHORT(dt.hundredths, dt.seconds);
     psz = lstrcat(psz, "----------- dTime=", pszLast);
     psz = itoa(Time - prevTime, psz, pszLast);
     psz = lstrcat(psz, " ", pszLast);
     prevTime = Time;
     return(psz + lstrlen(psz));
 }


 DMGSTRT.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGSTRT.ASM

 ;  DDE manager library initialization routine

 .286p

 ?WIN=1      ; Use Windows prolog/epilog
 ?PLM=1      ; Use PLM calling convention
 DOS5=1
 .xlist
 include cmacros.inc
 .list

 sBegin  DATA

 assumes DS,DATA

 externW hmodDmg
 externW usHugeShift

 sEnd    DATA

 sBegin  CODE
 assumes cs,CODE
 assumes ds,DATA

 externNP        SemInit

 ;
 ; Registers set up by DosLoadModule...
 ;
 ;   SI = heap size
 ;   DI = module ID
 ;   DS = library's automatic data segment
 ;
 cProc   LoadProc,<FAR,PUBLIC>
 cBegin  LoadProc
 ;       int     3
         mov     hmodDmg, di
         call    SemInit
 cEnd    LoadProc

 ;
 ; FillBlock(PBYTE pDst, USHORT cb, BYTE b)
 ;
 cProc        FillBlock,<PUBLIC, NEAR>,<DI, DS>
 ParmD        pDst
 ParmW        cb
 ParmW        b
 cBegin
         les        di,pDst
         mov        cx,cb
         mov        ax,b
         cld
         rep        stosb
 cEnd

 ;
 ; CopyBlock(pbSrc, pbDst, cb)
 ;
 LabelNP <PUBLIC, CopyBlock>
         mov     bx,sp
         push    si
         push    di
         mov     dx,ds               ; preserve DS

         mov     cx,ss:[bx+2]
         jcxz    copydone           ; all done if crc   == 0
         les     di,ss:[bx+2+2]
         lds     si,ss:[bx+2+2+4]
         cmp     si,di
         jae     copyok
         mov     ax,cx
         dec     ax
         add     si,ax
         add     di,ax
         std
         rep     movsb
         cld
         jmp     short copydone
 copyok:
         cld
         rep     movsb
 copydone:

         mov     ds,dx
         pop     di
         pop     si
         ret     10


 cProc HugeOffset,<NEAR, PUBLIC>
 parmD   pSrc
 parmD   cb
 cBegin
         mov     dx, SEG_cb
         mov     ax, OFF_pSrc
         add     ax, OFF_cb
         adc     dx, 0
         mov     cx, usHugeShift
         shl     dx, cl
         add     dx, SEG_pSrc
 cEnd


 LabelFP <PUBLIC, DdeDebugBreak>
         int     3
         retf    0

 ;
 ; Returns segment size or 0 on error.
 ;
 LabelNP <PUBLIC, CheckSel>
 ;    parmW   Selector    ; selector to validate
 cBegin  nogen
         mov     bx,sp               ; BX = selector to validate
         mov     bx,ss:[bx].2
         lar     ax,bx               ; See if valid selector
         jnz     invalid_selector

         lsl     ax,bx
         or      ax,ax               ; zero sized?
         jnz     valid_selector      ; nope, ok.

 invalid_selector:
         xor     ax,ax               ; Return zero just to be nice

 valid_selector:
         ret     2

 cEnd    nogen

 sEnd    CODE
 end     LoadProc


 DMGWNDP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\DMGWNDP.C

 /****************************** Module Header ******************************\
 *
 * Module Name: DMGWNDP.C
 *
 * This module contains all the window procs for the DDE manager.
 *
 * Created: 12/23/88 sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #include "ddemlp.h"


 ULONG defid = QID_SYNC;
 XFERINFO defXferInfo = {
         &defid,
         1L,
         XTYP_INIT,
         DDEFMT_TEXT,
         0L,
         0L,
         0L,
         NULL
 };

 void InitAck(HWND hwnd, PCLIENTINFO pci, HWND hwndServer, PDDEINIT pddei);
 MRESULT ClientXferReq(PXFERINFO pXferInfo, PCLIENTINFO pci, HWND hwnd);
 USHORT SendClientReq(PXADATA pXad, HWND hwndServer, HWND hwnd, PAPPINFO pai);
 void DoClientDDEmsg(PCLIENTINFO pci, HWND hwnd, USHORT msg, HWND hwndFrom,
         PDDESTRUCT pddes);
 BOOL fExpectedMsg(PXADATA pXad, PDDESTRUCT pddes, USHORT msg, PCLIENTINFO pci
 BOOL AdvanceXaction(HWND hwnd, PCLIENTINFO pci, PXADATA pXad,
         PDDESTRUCT pddes, USHORT msg, PUSHORT pErr);
 MRESULT ClientXferRespond(PCLIENTINFO pci, PXADATA pXad, PUSHORT pErr);
 void FrameInitConv(HWND hwndClient, PDDEINIT pddei);

 /*
  * ----------------CLIENT SECTION------------------
  *
  * Each client conversation has associated with it a window and a queue.
  * A conversation has one synchronous transaction and may have many
  * asynchronous transactions.  A transaction is differientiated by its
  * state and other pertinant data.  A transaction may be synchronous,
  * asynchronous, (initiated by DdeClientXfer()), or it may be external,
  * (initiated by an advise loop.)
  *
  * A transaction is active if it is in the middle of tranfer, otherwise
  * it is shutdown.  A shutdown transaction is either successful or
  * failed.  When an asynchronous transaction shuts down, the client
  * is notified via the callback function. (XTYP_XFERCOMPLETE)
  *
  * The synchronous transaction, when active, is in a timeout loop which
  * can shut-down the transaction at the end of a predefined time period.
  * Shutdown synchronous transactions imediately transfer their information
  * to the client application by returning to DdeClientXfer().
  *
  * asynchronous transactions remain in the client queue until removed
  * by the client application via DdeCheckQueue().
  *
  * external transactions take place when the client is in an advise
  * data loop.  These transactions pass through the callback function to
  * the client to be accepted.(XTYP_ADVDATA)
  */


 /***************************** Private Function ****************************\
 * MRESULT EXPENTRY ClientWndProc(hwnd, msg, mp1, mp2);
 *
 *   This window controls a single DDE conversation from the CLIENT side.
 *   If closed, it will automaticly abort any conversationn in progress.
 *   It maintains an internal list of any extra WM_DDEINITIATEACK messages
 *   it receives so that it can be queried later about this information.
 *   Any extra WM_DDEINITIATEACK messages comming in will be immediately
 *   terminated.
 *   It also maintains an internal list of all items which currently are
 *   in active ADVISE loops.
 *
 * History:
 *   Created     12/16/88    Sanfords
 \***************************************************************************/
 MRESULT EXPENTRY ClientWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     register PCLIENTINFO pci;
     PAPPINFO pai;
     MRESULT mrData;
     PDDESTRUCT pddes;

     pci = (PCLIENTINFO)WinQueryWindowULong(hwnd, QWL_USER);

     switch (msg) {
     case WM_CREATE:
         /*
          * allocate and initialize the client window info.
          */
         pai = GetCurrentAppInfo(FALSE);
         SemEnter();
         pci = (PCLIENTINFO)FarAllocMem(pai->hheapApp, sizeof(CLIENTINFO));
         SemLeave();
         if (pci == NULL) {
             pai->LastError = DMGERR_MEMORY_ERROR;
             return(1);          /* aboart creation - low memory */
         }
         WinSetWindowULong(hwnd, QWL_USER, (ULONG)pci);
         pci->ci.pai = pai;
         pci->ci.xad.state = CONVST_NULL;
         pci->ci.xad.pXferInfo = &defXferInfo;
         pci->ci.fs = 0;
         pci->ci.hwndPartner = NULL;
         pci->ci.hszServerApp = NULL;
         pci->ci.hszTopic = NULL;
         pci->pQ = NULL;    /* don't create until we need one */
         if (!(pci->ci.pAdviseList = CreateLst(pai->hheapApp, sizeof(ADVLI))))
             FarFreeMem(pai->hheapApp, (PBYTE)pci, sizeof(CLIENTINFO));
             pai->LastError = DMGERR_MEMORY_ERROR;
             return(1);          /* aboart creation - low memory */
         }
         break;

     case UMCL_INITIATE:
         if (pci->ci.xad.state == CONVST_NULL) {
             return(ClientInitiate(hwnd, (PINITINFO)mp1, pci));
         }
         break;

     case WM_DDE_INITIATEACK:
         InitAck(hwnd, pci, mp1, mp2);
         DosFreeSeg(SELECTOROF(mp2));
         return(1);
         break;

     case WM_DESTROY:
         SemCheckOut();
         if (pci->ci.fs & ST_CONNECTED) {
             /*
              * stop any advises in progress
              */
             if (pci->ci.fs & ST_ADVISE) {
                 pddes = AllocDDESel(0, 0, 0, 0L, NULL);
                 MyDdePostMsg(pci->ci.hwndPartner, hwnd, WM_DDE_UNADVISE,
                         (PMYDDES)pddes, pci->ci.pai, MDPM_FREEHDATA);
             }
             WinSendMsg(hwnd, UMCL_TERMINATE, 0L, 0L);
             /*
              * decrement the use counts on hszs we are done with.
              */
             FreeHsz(pci->ci.hszServerApp);
             FreeHsz(pci->ci.hszTopic);
         }

         DestroyLst(pci->ci.pAdviseList);

         SemEnter();
         DestroyQ(pci->pQ);
         FarFreeMem(pci->ci.pai->hheapApp, (PBYTE)pci, sizeof(CLIENTINFO));
         SemLeave();
         break;

     case UMCL_TERMINATE:
         /*
          * terminates any conversation in progress
          */
         if (pci->ci.fs & ST_CONNECTED) {
             pci->ci.fs = pci->ci.fs & ~ST_CONNECTED;
             pci->ci.xad.state = CONVST_TERMINATED;
             if (WinIsWindow(DMGHAB, pci->ci.hwndPartner))
                 WinDdePostMsg(pci->ci.hwndPartner, hwnd, WM_DDE_TERMINATE, 0L
         }
         break;

     case UMCL_XFER:
         if (!(pci->ci.fs & ST_CONNECTED)) {
             pci->ci.pai->LastError = DMGERR_NO_CONV_ESTABLISHED;
             return(0);
         }
         return(ClientXferReq((PXFERINFO)mp1, pci, hwnd));
         break;

     case WM_DDE_DATA:
     case WM_DDE_ACK:
         DoClientDDEmsg(pci, hwnd, msg, (HWND)mp1, (PDDESTRUCT)mp2);
         break;

     case WM_DDE_TERMINATE:
         SemCheckOut();
         /*
          * only respond if this is for us.
          */
         if ((HWND)mp1 != pci->ci.hwndPartner) {
            DosFreeSeg(SELECTOROF(mp2));
            break;
         }
         WinSendMsg(hwnd, UMCL_TERMINATE, 0L, 0L);
         DosFreeSeg(SELECTOROF(mp2));
         break;

     case UM_QUERY:
         /*
          * LOUSHORT(mp1) = info index.
          * mp2 = pData.     If pData==0, return data else copy into pData.
          */
         switch (LOUSHORT(mp1)) {
         case Q_STATUS:
              mrData = (MRESULT)pci->ci.fs;
              break;

         case Q_CLIENT:
              mrData = TRUE;
              break;

         case Q_APPINFO:
              mrData = pci->ci.pai;
              break;

         case Q_APPNAME:
              mrData = *(PHSZ)PTOPPILEITEM(pci->ci.pai->pAppNamePile);
              break;

         case Q_ALL:
              mrData = (MRESULT)(CLIENTINFO FAR *)pci;
              break;
         }
         if (mp2 == 0)
             return(mrData);
         else
             *(MRESULT FAR *)mp2 = mrData;
         return(1);
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }



 /*
  * Client response to a WM_DDE_INITIATEACK message when expected.
  */
 void InitAck(hwnd, pci, hwndServer, pddei)
 HWND hwnd;
 PCLIENTINFO pci;
 HWND hwndServer;
 PDDEINIT pddei;
 {
     SemCheckOut();

     if (pci->ci.fs & ST_CONNECTED) {
         /*
          * extra server - spawn another client window.  (we assume we
          * will only get extras if enumerating.)
          */
         AssertF(WinQueryWindow(hwnd, QW_PARENT, FALSE) != pci->ci.pai->hwndDm
             "Improper client spawn")
         if (hwndServer != pci->ci.hwndPartner) {
             WinSendMsg(hwndServer, WM_DDE_TERMINATE, hwnd, 0L);
             GetDDEClientWindow(WinQueryWindow(hwnd, QW_PARENT, FALSE),
                     hwndServer, hwndServer, pci->ci.hszServerApp,
                     pci->ci.hszTopic, &pci->ci.cc);
         }
         return;
     }

     if (pci->ci.xad.state != CONVST_INIT1)
         return;

     /*
      * first one back... lock in!
      */
     pci->ci.hwndPartner = hwndServer;
     pci->ci.xad.state = CONVST_CONNECTED;
     pci->ci.fs |= ST_CONNECTED;
     if (WinQueryWindowPtr(hwndServer, QWP_PFNWP) == ServerWndProc)
         pci->ci.fs |= ST_INTRADLL;

     /*
      * If the connection was made using a wild app name, we want to
      * hack in an apropriate name so QueryConvInfo can give the app
      * something useful to refer to this guy as.
      *
      * - the protocol is little help here.
      */
     if (pci->ci.hszServerApp == 0) {
         if (WinQueryWindowPtr(hwndServer, QWP_PFNWP) == ServerWndProc) {
             /*
              * one of ours! simple.
              */
             pci->ci.hszServerApp = (PAPPINFO)WinSendMsg(pci->ci.hwndPartner,
                     UM_QUERY, (MPARAM)Q_APPNAME, 0L);
         } else {
             /*
              * Try the psz in pddei.  Maybe the server set it properly
              * before returning it.
              */
             if (!(pci->ci.hszServerApp =
                     GetHsz(PSZAPP(pddei), pci->ci.cc.idCountry,
                             pci->ci.cc.usCodepage, TRUE))) {

                 PSZ pszT;
                 USHORT cb;

                 /*
                  * WORST CASE:
                  * Until a better way is found, we set the hszServerApp to
                  * the title of the frame window.
                  */
                 if (pszT = FarAllocMem(pci->ci.pai->hheapApp,
                         cb = WinQueryWindowTextLength(pci->ci.hwndFrame) + 1)
                     WinQueryWindowText(pci->ci.hwndFrame, cb, (PSZ)pszT);
                     pci->ci.hszServerApp = GetHsz(pszT, pci->ci.cc.idCountry,
                             pci->ci.cc.usCodepage, TRUE);
                     FarFreeMem(pci->ci.pai->hheapApp, (PBYTE)pszT, cb);
                 }
             }
         }
     }
     /*
      * Now what if the topic was wild?
      */
     if (pci->ci.hszTopic == 0) {
         if (WinQueryWindowPtr(hwndServer, QWP_PFNWP) == ServerWndProc) {
             /*
              * one of ours! simple.
              */
             pci->ci.hszTopic = (PAPPINFO)WinSendMsg(pci->ci.hwndPartner,
                     UM_QUERY, (MPARAM)Q_TOPIC, 0L);
         } else {
             /*
              * Try the psz in pddei.  Maybe the server set it properly
              * before returning it.  If this doesn't work were out of
              * luck, keep it wild.
              */
             pci->ci.hszServerApp = GetHsz(PSZAPP(pddei), pci->ci.cc.idCountry
                     pci->ci.cc.usCodepage, TRUE);
         }
     }
 }



 /***************************** Private Function ****************************\
 * Processes a client transfer request issued by one of the ClientXfer
 * functions.  This may be synchronous or asynchronous.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 MRESULT ClientXferReq(pXferInfo, pci, hwnd)
 PXFERINFO pXferInfo;
 PCLIENTINFO pci;
 HWND hwnd;
 {
     PCQDATA pcqd;
     MRESULT retVal;

     if (pXferInfo->ulTimeout == TIMEOUT_ASYNC) {
         /*
          * add a client queue item to track this transaction and return
          * the ID.
          */
         if (pci->pQ == NULL)
             pci->pQ = CreateQ(sizeof(CQDATA));
         if (pci->pQ == NULL) {
             pci->ci.pai->LastError = DMGERR_MEMORY_ERROR;
             return(0);
         }
         pcqd = (PCQDATA)Addqi(pci->pQ);
         if (pcqd == NULL) {
             pci->ci.pai->LastError = DMGERR_MEMORY_ERROR;
             return(0);
         }
         CopyBlock((PBYTE)pXferInfo, (PBYTE)&pcqd->XferInfo, sizeof(XFERINFO))
         pcqd->xad.state = CONVST_CONNECTED;
         pcqd->xad.pddes = 0;
         pcqd->xad.LastError = DMGERR_NO_ERROR;
         pcqd->xad.pXferInfo = &pcqd->XferInfo;
         /*
          * Get transaction started - if it fails, quit now.
          */
         if ((pcqd->xad.LastError = SendClientReq(&pcqd->xad,
                 pci->ci.hwndPartner, hwnd, pci->ci.pai)) == DMGERR_SERVER_DIE
             pci->ci.fs = pci->ci.fs & ~ST_CONNECTED;
             Deleteqi(pci->pQ, MAKEID(pcqd));
             pci->ci.pai->LastError = DMGERR_SERVER_DIED;
             return(0);
         }
         return((MRESULT)MAKEID(pcqd));
     }

     /*
      * if not quiesent, yet synchronous, tell him were busy.
      * (this case could happen on a recursive call.)
      */
     if (pci->ci.xad.state != CONVST_CONNECTED) {
         pci->ci.pai->LastError = DMGERR_BUSY;
         return(0);
     }
     /*
      * Set this so messages comming in during the conversation know whats up
      */
     pci->ci.xad.pXferInfo = pXferInfo;

     if ((pci->ci.pai->LastError = SendClientReq(&pci->ci.xad,
             pci->ci.hwndPartner, hwnd, pci->ci.pai)) == DMGERR_SERVER_DIED) {
         pci->ci.fs = pci->ci.fs & ~ST_CONNECTED;
     }

     if (pci->ci.pai->LastError != DMGERR_NO_ERROR)
         return(0);
     /*
      *  reset the LastError here so we know if we had problems while
      *  in the modal loop.
      */
     pci->ci.pai->LastError = DMGERR_NO_ERROR;
     /*
      * timeout - modal loop.
      */
     if (!timeout(pci->ci.pai, pXferInfo->ulTimeout, hwnd)) {
         /*
          * reentrency or client has unregistered
          */
         return(0);
     }
     /*
      * check results - lasterror already set by timeout().
      * Synchronous conversation must be reset to quiesent by the time we
      * give up.
      */
     if (pci->ci.xad.state == CONVST_INCOMPLETE) {
         pci->ci.xad.state = CONVST_CONNECTED;
         return(0);
     }

     retVal = ClientXferRespond(pci, &pci->ci.xad, &pci->ci.pai->LastError);
     if (pci->ci.xad.state == CONVST_INCOMPLETE)
         pci->ci.xad.state = CONVST_CONNECTED;

     return(retVal);
 }




 /***************************** Private Function ****************************\
 * This routine sends the apropriate initiation messages for starting a
 * client request according to the transaction data given.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 USHORT SendClientReq(pXad, hwndServer, hwnd, pai)
 PXADATA pXad;
 HWND hwndServer;
 HWND hwnd;
 PAPPINFO pai;
 {
     USHORT fsStatus = 0;
     USHORT msg;
     BOOL fCopy;
     PDDESTRUCT pddes;

     switch (pXad->pXferInfo->usType) {
     case XTYP_REQUEST:
         msg = WM_DDE_REQUEST;
         pXad->state = CONVST_REQSENT;
         fCopy = FALSE;
         break;

     case XTYP_POKE:
         msg = WM_DDE_POKE;
         pXad->state = CONVST_POKESENT;
         fCopy = TRUE;
         break;

     case XTYP_EXEC:
         msg = WM_DDE_EXECUTE;
         pXad->state = CONVST_EXECSENT;
         fCopy = TRUE;
         break;

     case XTYP_ADVSTART:
     case XTYP_ADVSTART | XTYPF_NODATA:
     case XTYP_ADVSTART | XTYPF_ACKREQ:
     case XTYP_ADVSTART | XTYPF_NODATA | XTYPF_ACKREQ:
         fsStatus = pXad->pXferInfo->usType & (DDE_FACKREQ | DDE_FNODATA);
         msg = WM_DDE_ADVISE;
         pXad->state = CONVST_ADVSENT;
         fCopy = FALSE;
         break;

     case XTYP_ADVSTOP:
         msg = WM_DDE_UNADVISE;
         pXad->state = CONVST_UNADVSENT;
         fCopy = FALSE;
         break;

     default:
         return(DMGERR_INVALIDPARAMETER);
         break;
     }

     /*
      * Send transfer
      */
     if ((pddes = AllocDDESel(fsStatus, pXad->pXferInfo->usFmt,
             pXad->pXferInfo->hszItem, fCopy ? pXad->pXferInfo->cb : 0, NULL))
             == 0) {
         pXad->state = CONVST_CONNECTED;
         return(DMGERR_MEMORY_ERROR);
     }

     if (fCopy)
         CopyHugeBlock((PBYTE)pXad->pXferInfo->pData, DDES_PABDATA(pddes),
                 pXad->pXferInfo->cb);

     if (WinIsWindow(DMGHAB, hwndServer)) {
         if (!MyDdePostMsg(hwndServer, hwnd, msg, (PMYDDES)pddes, pai, MDPM_FR
             pXad->state = CONVST_CONNECTED;
             return(DMGERR_POSTMSG_FAILED);
         }
     } else {
         /*
          * We lost the server, we are TERMINATED arnold!
          */
         pXad->state = CONVST_TERMINATED;
         FreeData((PMYDDES)pddes, pai);
         return(DMGERR_SERVER_DIED);
     }
     return(DMGERR_NO_ERROR);
 }





 /***************************** Private Function ****************************\
 * This handles client window processing of WM_DDE_ACK and WM_DDE_DATA msgs.
 * On exit pddes is freed.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 void DoClientDDEmsg(pci, hwnd, msg, hwndFrom, pddes)
 PCLIENTINFO pci;
 HWND hwnd;
 USHORT msg;
 HWND hwndFrom;
 PDDESTRUCT pddes;
 {
     PCQDATA pqd;
     int i;
     HSZ hszItem;
     PADVLI pAdviseItem;

     /*
      * make sure its for us.
      */
     if (hwndFrom != pci->ci.hwndPartner || !(pci->ci.fs & ST_CONNECTED)) {
         FreeData((PMYDDES)pddes, pci->ci.pai);
         return;
     }
     /*
      * Check if it fits the synchronous transaction data
      */
     if (fExpectedMsg(&pci->ci.xad, pddes, msg, pci)) {
         if (AdvanceXaction(hwnd, pci, &pci->ci.xad, pddes, msg,
                 &pci->ci.pai->LastError))
             WinPostMsg(hwnd, WM_TIMER, (MPARAM)TID_TIMEOUT, 0L);
         return;
     }

     /*
      * See if it fits any asynchronous transaction data - if any exist
      */
     if (pci->pQ != NULL && pci->pQ->pqiHead != NULL) {
         SemEnter();
         pqd = (PCQDATA)pci->pQ->pqiHead;
         /*
          * cycle from oldest to newest.
          */
         for (i = pci->pQ->cItems; i; i--) {
             pqd = (PCQDATA)pqd->next;
             if (!fExpectedMsg(&pqd->xad, pddes, msg, pci))
                 continue;
             if (AdvanceXaction(hwnd, pci, &pqd->xad, pddes, msg,
                     &pqd->xad.LastError)) {
                 ClientXferRespond(pci, &pqd->xad, &pqd->xad.LastError);
                 SemLeave();
                 MakeCallback(pci->ci.pai, hwnd, (HSZ)0L, (HSZ)0L, 0,
                         XTYP_XFERCOMPLETE, (HDMGDATA)MAKEID(pqd),
                         0, 0, hwndFrom);
                 return;
             }
             SemLeave();
             return;
         }
         SemLeave();
     }
     /*
      * It doesn't fit anything, check for an advise data message.
      */
     if (msg == WM_DDE_DATA) {
         hszItem = GetHszItem((PMYDDES)pddes, &pci->ci.cc, TRUE);
         if (pAdviseItem = (PADVLI)FindAdvList(pci->ci.pAdviseList, hszItem,
                 pddes->usFormat)) {
             MakeCallback(pci->ci.pai, (HCONV)hwnd, pci->ci.hszTopic,
                 hszItem, pddes->usFormat, XTYP_ADVDATA, pddes, msg,
                 pddes->fsStatus, pci->ci.hwndPartner);
         } else {
             FreeHsz(hszItem);
         }
         return;
     }
     /*
      * throw it away
      */
     FreeData((PMYDDES)pddes, pci->ci.pai);
     return;
 }



 /***************************** Private Function ****************************\
 * This routine matches a conversation transaction with a DDE message.  If
 * the state, usType, format, itemname dde structure data and the message
 * received all agree, TRUE is returned.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 BOOL fExpectedMsg(pXad, pddes, msg, pci)
 PXADATA pXad;
 PDDESTRUCT pddes;
 USHORT msg;
 PCLIENTINFO pci;
 {
     HSZ hsz = 0;
     BOOL fRet = FALSE;

     if (!(pXad->state > CONVST_INIT1 &&
             pddes->usFormat == pXad->pXferInfo->usFmt &&
             (hsz = GetHszItem((PMYDDES)pddes, &pci->ci.cc, TRUE)) ==
             pXad->pXferInfo->hszItem)) {
         goto Exit;
     }
     switch (pXad->state) {
     case CONVST_REQSENT:
         if (msg == WM_DDE_DATA && !(pddes->fsStatus & DDE_FRESPONSE))
             /*
              * Not data in response to a request!
              */
             break;
         fRet = (msg == WM_DDE_ACK || msg == WM_DDE_DATA);
         break;

     case CONVST_POKESENT:
     case CONVST_EXECSENT:
     case CONVST_ADVSENT:
     case CONVST_UNADVSENT:
         fRet = (msg == WM_DDE_ACK);
         break;
     }

 Exit:
     FreeHsz(hsz);
     return(fRet);
 }



 /***************************** Private Function ****************************\
 * This function assumes that msg is an apropriate message for the transaction
 * referenced by pXad.  It acts on msg as apropriate.  pddes is the DDESTRUCT
 * associated with msg.
 *
 * Returns fSuccess ie: transaction is ready to close up.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 BOOL AdvanceXaction(hwnd, pci, pXad, pddes, msg, pErr)
 HWND hwnd;
 PCLIENTINFO pci;
 PXADATA pXad;
 PDDESTRUCT pddes;
 USHORT msg;
 PUSHORT pErr;
 {
     switch (msg) {
     case WM_DDE_ACK:
         switch (pXad->state) {
         case CONVST_ADVSENT:
         case CONVST_EXECSENT:
         case CONVST_POKESENT:
         case CONVST_REQSENT:
         case CONVST_UNADVSENT:
             if (pddes->fsStatus & DDE_FACK) {
                 /*
                  * handle successes
                  */
                 switch (pXad->state) {
                 case CONVST_POKESENT:
                     pXad->state = CONVST_POKEACKRCVD;
                     break;

                 case CONVST_EXECSENT:
                     pXad->state = CONVST_EXECACKRCVD;
                     break;

                 case CONVST_ADVSENT:
                     pXad->state = CONVST_ADVACKRCVD;
                     break;

                 case CONVST_UNADVSENT:
                     pXad->state = CONVST_UNADVACKRCVD;
                     break;

                 case CONVST_REQSENT:
                     /*
                      * requests are not expected to send a +ACK.  only
                      * -ACK or data.  We ignore a +ACK to a request.
                      */
                     FreeData((PMYDDES)pddes, pci->ci.pai);
                     return(FALSE);
                 }
             } else {
                 /*
                  * handle the expected ACK failures.
                  */
                 *pErr = DMGERR_NOTPROCESSED;
                 if (pddes->fsStatus & DDE_FBUSY)
                     *pErr = DMGERR_BUSY;
                 pXad->state = CONVST_INCOMPLETE;
             }
         }
         FreeData((PMYDDES)pddes, pci->ci.pai);
         return(TRUE);
         break;

     case WM_DDE_DATA:
         switch (pXad->state) {
         case CONVST_REQSENT:
             /*
              * send an ack if requested - we dare not return the given
              * pddes because it may be a data item sent to several
              * clients and we would mess up the fsStatus word for
              * all processes involved.
              */
             if (pddes->fsStatus & DDE_FACKREQ) {
                 MyDdePostMsg(pci->ci.hwndPartner, hwnd, WM_DDE_ACK,
                         (PMYDDES)AllocDDESel(DDE_FACK, pddes->usFormat,
                         pXad->pXferInfo->hszItem, 0L, NULL),
                         pci->ci.pai, MDPM_FREEHDATA);
             }
             pXad->state = CONVST_DATARCVD;
             /*
              * We do NOT free the selector here yet because it will be
              * given to the client via pXad->pddes.
              */
             pXad->pddes = pddes;
             return(TRUE);
             break;
         }
     }
     return(FALSE);
 }



 /***************************** Private Function ****************************\
 * This function assumes that a client transfer request has been completed -
 * or should be completed by the time this is called.
 *
 * pci contains general client info
 * pXad contains the transaction info
 * pErr points to where to place the LastError code.
 *
 * Returns 0 on failure
 * Returns TRUE or a Data Selector on success.
 * On failure, the conversation is left in a CONVST_INCOMPLETE state.
 * On success, the conversation is left in a CONVST_CONNECTED state.
 *
 * History:
 *   Created     9/1/89    Sanfords
 \***************************************************************************/
 MRESULT ClientXferRespond(pci, pXad, pErr)
 PCLIENTINFO pci;
 PXADATA pXad;
 PUSHORT pErr;
 {
     if (pXad->state == CONVST_INCOMPLETE)
         return(0);

     switch (pXad->pXferInfo->usType) {
     case XTYP_REQUEST:
         if (pXad->state != CONVST_DATARCVD) {
             if (*pErr == DMGERR_NO_ERROR)
                 *pErr = DMGERR_DATAACKTIMEOUT;
             goto failexit;
         }
         pXad->state = CONVST_CONNECTED;
         return(pXad->pddes);
         break;

     case XTYP_POKE:
         if (pXad->state != CONVST_POKEACKRCVD) {
             if (*pErr == DMGERR_NO_ERROR)
                 *pErr = DMGERR_POKEACKTIMEOUT;
             goto failexit;
         }
         pXad->state = CONVST_CONNECTED;
         return(TRUE);
         break;

     case XTYP_EXEC:
         if (pXad->state != CONVST_EXECACKRCVD) {
             if (*pErr == DMGERR_NO_ERROR)
                 *pErr = DMGERR_EXECACKTIMEOUT;
             goto failexit;
         }
         pXad->state = CONVST_CONNECTED;
         return(TRUE);
         break;

     case XTYP_ADVSTART:
     case XTYP_ADVSTART | XTYPF_NODATA:
     case XTYP_ADVSTART | XTYPF_ACKREQ:
     case XTYP_ADVSTART | XTYPF_NODATA | XTYPF_ACKREQ:
         if (pXad->state != CONVST_ADVACKRCVD) {
             if (*pErr == DMGERR_NO_ERROR)
                 *pErr = DMGERR_ADVACKTIMEOUT;
             goto failexit;
         }
         if (!AddAdvList(pci->ci.pAdviseList, pXad->pXferInfo->hszItem,
                 pXad->pXferInfo->usType & (DDE_FACKREQ | DDE_FNODATA),
                 pXad->pXferInfo->usFmt)) {
             pXad->state = CONVST_INCOMPLETE;
             pci->ci.pai->LastError = DMGERR_MEMORY_ERROR;
             return(FALSE);
         } else {
             pXad->state = CONVST_CONNECTED;
             pci->ci.fs |= ST_ADVISE;
             return(TRUE);
         }
         break;

     case XTYP_ADVSTOP:
         if (pXad->state != CONVST_UNADVACKRCVD) {
             if (*pErr == DMGERR_NO_ERROR)
                 *pErr = DMGERR_UNADVACKTIMEOUT;
             goto failexit;
         }
         if (!DeleteAdvList(pci->ci.pAdviseList, pXad->pXferInfo->hszItem,
                 pXad->pXferInfo->usFmt))
             pci->ci.fs &= ~ST_ADVISE;
         pXad->state = CONVST_CONNECTED;
         return(TRUE);
         break;

     }

 failexit:
     pXad->state = CONVST_INCOMPLETE;
     return(0);
 }






 /*
  * ----------------------------SERVER SECTION--------------------------------
  */



 /***************************** Public  Function ****************************\
 * MRESULT EXPENTRY ServerWndProc(hwnd, msg, mp1, mp2)
 * HWND hwnd;
 * USHORT msg;
 * MPARAM mp1;
 * MPARAM mp2;
 *
 * DESCRIPTION:
 *   This processes DDE conversations from the server end.
 *   It stores internal information and acts much like a state machine.
 *   If closed, it will automaticly abort any conversation in progress.
 *   It also maintains an internal list of all items which currently are
 *   in active ADVISE loops.
 * PUBDOC START
 *   These server windows have the feature that a conversation can be
 *   re-initiated with them by a client.  The Client merely terminates
 *   the conversation and then re-initiates by using a SendMsg to this
 *   window.  This allows a client to change the topic of the conversation
 *   or to pass the conversation on to another client window without
 *   loosing the server it initiated with.   This is quite useful for
 *   wild initiates.
 * PUBDOC END
 *
 * History:
 * 10/18/89 sanfords Added hack to make hszItem==0L when offszItem==offabData.
 * 1/4/89   sanfords created
 \***************************************************************************/
 MRESULT EXPENTRY ServerWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
 #define PDDES ((PDDESTRUCT)mp2)
     register PSERVERINFO psi;
     MPARAM mrData;
     PADVLI pAdviseItem;
     PSZ pszApp, pszTopic;
     HSZ hsz;
     USHORT cchApp, cchTopic;
     USHORT usType;
     HDMGDATA hDmgData = 0L;
     BOOL fResult;


     psi = (PSERVERINFO)WinQueryWindowULong(hwnd, QWL_USER);

     switch (msg) {
     case WM_DDE_REQUEST:
     case WM_DDE_ACK:
     case WM_DDE_ADVISE:
     case WM_DDE_UNADVISE:
     case WM_DDE_POKE:
     case WM_DDE_EXECUTE:
         /*
          * only respond if this is for us.
          */
         if ((HWND)mp1 != psi->ci.hwndPartner || !(psi->ci.fs & ST_CONNECTED))
             FreeData((PMYDDES)mp2, psi->ci.pai);
             return(0);
         }
     }

     switch (msg) {
     case WM_CREATE: {
             PAPPINFO pai;

             /*
              * allocate and initialize the server window info.
              */
             pai = GetCurrentAppInfo(FALSE);
             SemEnter();

             if (!(psi = (PSERVERINFO)FarAllocMem(pai->hheapApp, sizeof(SERVER
                 goto LowMem;
             FillBlock((PBYTE)&psi->ci, sizeof(COMMONINFO), 0);
             if (!(psi->ci.pAdviseList = CreateLst(pai->hheapApp, sizeof(ADVLI
                 FarFreeMem(pai->hheapApp, (PBYTE)psi, sizeof(SERVERINFO));
 LowMem:
                 pai->LastError = DMGERR_MEMORY_ERROR;
                 SemLeave();
                 return(1);          /* abort creation - low memory */
             }
             SemLeave();
             psi->ci.pai = pai;
             psi->ci.xad.state = CONVST_NULL;
             WinSetWindowULong(hwnd, QWL_USER, (ULONG)psi);
         }
         break;

     case UMSR_INITIATE:
         /*
          * This was sent by the subclassed frame of the server app.
          * The frame has already queried the server for permission
          * to create this window.
          *
          * If mp2 is NULL, this is a hot server window waiting for
          * a WM_DDE_INITIATE.
          */
 #define pii ((PINITINFO)mp1)
         IncHszCount(psi->ci.hszServerApp = pii->hszAppName);
         IncHszCount(psi->ci.hszTopic = pii->hszTopic);
         psi->ci.hwndPartner = (HWND)mp2;
         psi->ci.hwndFrame = FindFrame(psi->ci.hwndPartner);
         psi->ci.cc.fsContext = pii->pCC->fsContext;
         psi->ci.cc.idCountry = pii->pCC->idCountry;
         psi->ci.cc.usCodepage = pii->pCC->usCodepage;
         psi->ci.fs |= ST_CONNECTED;
         psi->ci.xad.state = CONVST_CONNECTED;

         SemEnter();
         pszApp = pszFromHsz(psi->ci.hszServerApp, &cchApp);
         pszTopic = pszFromHsz(psi->ci.hszTopic, &cchTopic);
         SemLeave();

         if (mp2)
             WinDdeRespond((HWND)mp2, hwnd, pszApp, pszTopic);

         SemEnter();
         FarFreeMem(hheapDmg, (PBYTE)pszApp, cchApp);
         FarFreeMem(hheapDmg, (PBYTE)pszTopic, cchTopic);
         SemLeave();

         return(1);
 #undef pii
         break;

     case WM_DDE_INITIATE:
         /*
          * This will happen when a client tries to re-initiate a conversation
          * with this server.  We allow about 10 seconds after termination
          * for another client to connect specifically with this window.
          * This allows a client to swap windows on its end of the conversatio
          */

         if (psi->ci.xad.state == CONVST_TERMINATED &&
                 (psi->ci.hszServerApp == GetHsz(PSZAPP(mp2), psi->ci.cc.idCou
                         psi->ci.cc.usCodepage, FALSE))) {

             WinStopTimer(DMGHAB, hwnd, TID_SELFDESTRUCT);
             hsz = psi->ci.hszTopic;
             psi->ci.hszTopic = GetHsz(PSZTOPIC(mp2), psi->ci.cc.idCountry,
                     psi->ci.cc.usCodepage, TRUE);
             FreeHsz(hsz);
             psi->ci.hwndPartner = (HWND)mp1;
             psi->ci.hwndFrame = FindFrame(psi->ci.hwndPartner);
             psi->ci.fs |= ST_CONNECTED;
             psi->ci.xad.state = CONVST_CONNECTED;
             if (WinQueryWindowPtr(psi->ci.hwndPartner, QWP_PFNWP) == ClientWn
                 psi->ci.fs |= ST_INTRADLL;
             WinDdeRespond((HWND)mp1, hwnd, PSZAPP(mp2), PSZTOPIC(mp2));
             fResult = TRUE;
         } else
             fResult = FALSE;

         FreeData((PMYDDES)mp2, psi->ci.pai);
         return(fResult);
         break;

     case WM_DDE_TERMINATE:
         /*
          * only respond if this is for us.
          */
         if ((HWND)mp1 != psi->ci.hwndPartner)
             break;
         /* fall through */

     case UMSR_TERMINATE:
         /*
          * terminates any conversation in progress
          * Note that we keep around all the other conversation data so
          * a later re-connection is possible.
          */
         if (psi->ci.fs & ST_CONNECTED) {
             psi->ci.fs &= ~ST_CONNECTED;
             psi->ci.xad.state = CONVST_TERMINATED;
             if (WinIsWindow(DMGHAB, psi->ci.hwndPartner))
                 WinDdePostMsg(psi->ci.hwndPartner, hwnd, WM_DDE_TERMINATE, 0L
         }
         if (psi->ci.fs & ST_ADVISE) {
             FlushLst(psi->ci.pAdviseList);
             psi->ci.fs &= ~ST_ADVISE;
         }
         /*
          * Mr. Phelps, if this window isn't reconnected within 10 odd
          * seconds, it will self-destruct.  This gives the client time
          * to reconnect with another client window.  This often happens
          * with wild initiates.
          */
         WinStartTimer(DMGHAB, hwnd, TID_SELFDESTRUCT, 0xa000);
         break;

     case WM_TIMER:
         if (LOUSHORT(mp1) == TID_SELFDESTRUCT && !(psi->ci.fs & ST_CONNECTED)
             DestroyWindow(hwnd);
         break;

     case WM_DESTROY:
         SemCheckOut();
         /*
          * Send ourselves a terminate and free local data.
          */
         WinSendMsg(hwnd, UMSR_TERMINATE, 0L, 0L);
         MakeCallback(psi->ci.pai, hwnd, psi->ci.hszTopic, (HSZ)NULL, 0, XTYP_
                 0L, 0, 0, psi->ci.hwndPartner);
         SemEnter();
         DestroyLst(psi->ci.pAdviseList);
         FreeHsz(psi->ci.hszServerApp);
         FreeHsz(psi->ci.hszTopic);
         FarFreeMem(psi->ci.pai->hheapApp, (PBYTE)psi, sizeof(SERVERINFO));
         SemLeave();
         break;

     case WM_DDE_REQUEST:
         usType = XTYP_REQUEST;
         goto Callback;

     case WM_DDE_EXECUTE:
         usType = XTYP_EXEC;
         hDmgData = mp2;
         goto Callback;

     case WM_DDE_POKE:
         usType = XTYP_POKE;
         hDmgData = mp2;
         goto Callback;

     case WM_DDE_ADVISE:
         usType = XTYP_ADVSTART; /* set ST_ADVISE AFTER app oks advise loop */
         goto Callback;

     case WM_DDE_UNADVISE:
         /*
          * Terminate the advise now, but notify the server in callback so
          * messages don't get out of order.
          */
         if (!DeleteAdvList(psi->ci.pAdviseList,
                 GetHszItem(mp2, &psi->ci.cc, FALSE),
                 PDDES->usFormat))
             psi->ci.fs &= ~ST_ADVISE;
         usType = XTYP_ADVSTOP;
         goto Callback;

     case WM_DDE_ACK:
         /*
          * This is an ack in response to the FACKREQ bit being set.
          * See if this refers to one of the advise loops.
          */
         if ((pAdviseItem = FindAdvList(psi->ci.pAdviseList,
                 GetHszItem(mp2, &psi->ci.cc, FALSE),
                 PDDES->usFormat)) &&
                 (pAdviseItem->fsStatus & DDE_FACKREQ)) {
             /*
              * Update advise loop status - no longer waiting for an ack.
              */
             pAdviseItem->fsStatus &= ~ADVST_WAITING;
             if (pAdviseItem->fsStatus & ADVST_CHANGED) {
                 pAdviseItem->fsStatus |= ADVST_POSTED;
                 /*
                  * The client is out of date.  Send the data
                  * again (simulate a post advise call).
                  * Don't bother the server with ACK info.
                  */
                 MakeCallback(psi->ci.pai, (HCONV)hwnd, psi->ci.hszTopic,
                         pAdviseItem->hszItem,
                         pAdviseItem->usFmt, XTYP_ADVREQ,
                         0L, UMSR_POSTADVISE,
                         pAdviseItem->fsStatus & ~DDE_FRESERVED,
                         psi->ci.hwndPartner);
                 FreeData((PMYDDES)mp2, psi->ci.pai);
                 return(0);
             }
         }
         usType = XTYP_ACK;
         hDmgData = PDDES->fsStatus;

 Callback:
         MakeCallback(psi->ci.pai, (HCONV)hwnd, psi->ci.hszTopic,
 #if 0
                 /*
                  * hack for EXCEL which makes its items and data equal for
                  * execute acks which SHOULD use NULL as the item name.
                  */
                 (PDDES->offszItemName == PDDES->offabData) ?
                     0L :
 #endif
                 GetHszItem((PMYDDES)mp2, &psi->ci.cc, TRUE),
                 PDDES->usFormat, usType,
                 hDmgData, msg, PDDES->fsStatus,
                 psi->ci.hwndPartner);
         /*
          * now free the incomming selector IF it wasn't passed on to
          * MakeCallback as hDmgData.
          */
         if (hDmgData != mp2)
             FreeData((PMYDDES)mp2, psi->ci.pai);
         break;

     case UMSR_POSTADVISE:
         /*
          * This message came from DdePostAdvise()
          *
          * Advise loops are tricky because of the desireable FACKREQ feature
          * of DDE.  The advise loop list holds information in its fsStatus
          * field to maintain the state of the advise loop.
          *
          * if the ADVST_POSTED bit is set, it means that the server already
          * has an ADVREQ message in its callback queue.  This prevents
          * unnecessary ADVREQ messages from getting thrown into the callback
          * queue.
          *
          * if the ADVST_WAITING bit is set, the server is still waiting for
          * the client to give it the go-ahead for more data with an
          * ACK message on this item. (FACKREQ is set)  Without a go-ahead,
          * the server will not send any more advise data to the client but
          * will instead set the ADVST_CHANGED bit which will cause another
          * WM_DDE_DATA message to be sent to the client as soon as the
          * go-ahead ACK is received.  This keeps the client up to date
          * but never overloads it.
          */

         if (!(psi->ci.fs & ST_ADVISE) ||
                 !(pAdviseItem = FindAdvList(psi->ci.pAdviseList, (HSZ)mp1, 0)
             break;

         do {
             /*
              * for each format for this item that has an advise loop:
              */
             if (pAdviseItem->fsStatus & ADVST_POSTED)
                 continue;

             if ((pAdviseItem->fsStatus & DDE_FACKREQ) &&
                     (pAdviseItem->fsStatus & ADVST_WAITING)) {
                 /*
                  * if the client has not yet finished with the last data
                  * we gave him, just update the advise loop status
                  * instead of sending data now.
                  */
                 pAdviseItem->fsStatus |= ADVST_CHANGED;
                 continue;
             }
             if (pAdviseItem->fsStatus & DDE_FNODATA) {
                 /*
                  * In the nodata case, we don't bother the server.  Just
                  * pass the client an apropriate DATA message.
                  */
                 MyDdePostMsg(psi->ci.hwndPartner, hwnd, WM_DDE_DATA,
                         (PMYDDES)AllocDDESel(pAdviseItem->fsStatus & ~(DDE_FN
                         pAdviseItem->usFmt, (HSZ)mp1, 0L, 0),
                         psi->ci.pai, MDPM_FREEHDATA);
                 continue;
             }
             /*
              * Otherwise, lets get the data from the server.
              */
             pAdviseItem->fsStatus |= ADVST_POSTED;
             MakeCallback(psi->ci.pai, (HCONV)hwnd, psi->ci.hszTopic,
                     (HSZ)mp1, pAdviseItem->usFmt, XTYP_ADVREQ,
                     0, UMSR_POSTADVISE,
                     pAdviseItem->fsStatus & (DDE_FACKREQ | DDE_FNODATA),
                     psi->ci.hwndPartner);
         } while (pAdviseItem = FindNextAdv(pAdviseItem, (HSZ)mp1));
         break;

     case UM_QUERY:
         /*
          * LOUSHORT(mp1) = info index.
          * mp2 = pData.     If pData==0, return data else copy into pData.
          */
         switch (LOUSHORT(mp1)) {
         case Q_STATUS:
              mrData = (MRESULT)psi->ci.fs;
              break;

         case Q_CLIENT:
              mrData = FALSE;
              break;

         case Q_APPINFO:
              mrData = psi->ci.pai;
              break;

         case Q_APPNAME:
              mrData = psi->ci.hszServerApp;
              break;

         case Q_TOPIC:
              mrData = psi->ci.hszTopic;
              break;

         case Q_ALL:
              mrData = (MRESULT)(SERVERINFO FAR *)psi;
              break;
         }
         if (mp2 == 0)
             return(mrData);
         else
             *(MRESULT FAR *)mp2 = mrData;
         return(1);
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 #undef PDDES
 }




 /*
  * This assumes hwnd is a DDE window.  It tries to locate the proper
  * top-level frame window that this window is associated with useing
  * process and thread IDs.
  */
 HWND FindFrame(
 HWND hwnd)
 {
     PID pid, pidFrame;
     TID tid, tidFrame;
     HWND hwndMaybe = NULL;
     HWND hwndBetter = NULL;
     HWND hwndFrame;
     HENUM hEnum;
     ULONG ul;

     WinQueryWindowProcess(hwnd, &pid, &tid);
     hEnum = WinBeginEnumWindows(HWND_DESKTOP);
     while (hwndFrame = WinGetNextWindow(hEnum)) {
         /*
          * for all top level windows ...
          */
         ul = (ULONG)WinSendMsg(hwndFrame, WM_QUERYFRAMEINFO, 0L, 0L);
         if (FI_FRAME & ul) {
             /*
              * that are frames ...
              */
             WinQueryWindowProcess(hwndFrame, &pidFrame, &tidFrame);
             if (pidFrame == pid) {
                 /*
                  * in this process - maybe - ...
                  */
                 hwndMaybe = hwndFrame;
                 if (tidFrame == tid) {
                     /*
                      * in this thread - better - ...
                      */
                     hwndBetter = hwndFrame;
                     if (WinQueryWindowPtr(hwndFrame, QWP_PFNWP) ==
                             subframeWndProc) {
                         /*
                          * that are subclassed by us - certainly!
                          */
                         hwndBetter = hwndFrame;
                         break;
                     }
                 }
             }
         }
     }
     WinEndEnumWindows(hEnum);
     return(hwndBetter ? hwndBetter : hwndMaybe);
 }




 /***************************** Private Function ****************************\
 * This routine handles server message replys.  This may have been called
 * immediately in the case of enabled callbacks, or may have been called
 * via DdeEnableCallback in which case the server action has been
 * delayed.  QReply is responsible for freeing the pddes given as well as
 * the pcbi->hDmgData and pcbi->hszItem.
 *
 * History:
 *   Created     9/12/89    Sanfords
 *   6/12/90 sanfords    Added checks for HDATA ownership.
 \***************************************************************************/
 void QReply(pcbi, pddes)
 PCBLI pcbi;
 PDDESTRUCT pddes;   /* hDataRet */
 {
     PSERVERINFO psi;
     PADVLI pAdviseItem;
     USHORT fsStatus, msg;

     if ((pcbi->usType & XCLASS_MASK) == XCLASS_NOTIFICATION)
         return;

     SemCheckOut();
     psi = WinSendMsg(pcbi->hConv, UM_QUERY, (MPARAM)Q_ALL, 0L);

     switch (pcbi->msg) {
     case UMSR_POSTADVISE:
         /*
          * The NODATA case never gets here.
          */
         if ((psi) &&
                 (pAdviseItem = FindAdvList(psi->ci.pAdviseList, pcbi->hszItem
                 pcbi->usFmt))) {
             pAdviseItem->fsStatus &= ~ADVST_POSTED;
             if (pddes) {
                 pAdviseItem->fsStatus &= ~ADVST_CHANGED;
                 MyDdePostMsg(pcbi->hConvPartner, pcbi->hConv, WM_DDE_DATA,
                         (PMYDDES)pddes, psi->ci.pai, MDPM_FREEHDATA);
                 if (pAdviseItem->fsStatus & DDE_FACKREQ)
                     pAdviseItem->fsStatus |= ADVST_WAITING;
             }
         }
         break;

     case WM_DDE_REQUEST:
         if (pddes) {
             pddes->fsStatus = (pcbi->fsStatus & DDE_FACKREQ) | DDE_FRESPONSE;
             msg = WM_DDE_DATA;
         } else {
             /*
              * send a -ACK
              */
             pddes = AllocDDESel(((USHORT)pddes & DDE_FAPPSTATUS) |
                     ((USHORT)pddes & DDE_FBUSY ? DDE_FBUSY : DDE_NOTPROCESSED
                     pcbi->usFmt, pcbi->hszItem, 0L, NULL);
             msg = WM_DDE_ACK;
         }
         MyDdePostMsg(pcbi->hConvPartner, pcbi->hConv, msg, (PMYDDES)pddes,
                 psi->ci.pai, MDPM_FREEHDATA);
         break;

     case WM_DDE_POKE:
     case WM_DDE_EXECUTE:
         /*
          * pddes is supposed to be the proper DDE_ constants to return.
          * we just stick them in the given pddes (hDmgData) and return
          * it as an ACK.  This frees pcbi->hDmgData in the process.
          */
         ((PDDESTRUCT)pcbi->hDmgData)->fsStatus =
                 (USHORT)pddes & ~DDE_FRESERVED;
         MyDdePostMsg(pcbi->hConvPartner, pcbi->hConv, WM_DDE_ACK,
                 (PMYDDES)pcbi->hDmgData, psi->ci.pai, MDPM_FREEHDATA);
         break;

     case WM_DDE_ADVISE:
         /*
          * pddes is fStartAdvise
          * If DDE_FACK, we add the item to the advise loop
          * list and +ACK else we -ACK.
          */
         if ((BOOL)pddes) {
             psi = (PSERVERINFO)WinQueryWindowULong(pcbi->hConv, QWL_USER);
             if (AddAdvList(psi->ci.pAdviseList, pcbi->hszItem,
                     pcbi->fsStatus & (DDE_FNODATA | DDE_FACKREQ),
                     pcbi->usFmt) == NULL) {
                 psi->ci.pai->LastError = DMGERR_MEMORY_ERROR;
                 fsStatus = DDE_NOTPROCESSED;
             } else {
                 psi->ci.fs |= ST_ADVISE;
                 fsStatus = DDE_FACK;
             }
         } else {
             fsStatus = DDE_NOTPROCESSED;
         }
         goto AckBack;
         break;

     case WM_DDE_UNADVISE:
         fsStatus = DDE_FACK;
         goto AckBack;
         break;

     case WM_DDE_DATA:
         /*
          * must be an advise data item for the CLIENT or maybe some requested
          * data mistakenly sent here due to the client queue being flushed.
          * pddes is fsStatus.
          *
          * send an ack back if requested.
          */
         if (pcbi->fsStatus & DDE_FACKREQ) {
             /*
              * Clean up the status incase the app is messed up.
              */
             fsStatus = (USHORT)pddes & ~DDE_FRESERVED;
             if (fsStatus & (DDE_NOTPROCESSED | DDE_FBUSY))
                 fsStatus &= ~DDE_FACK;
 AckBack:
             MyDdePostMsg(pcbi->hConvPartner, pcbi->hConv, WM_DDE_ACK,
                 (PMYDDES)AllocDDESel(fsStatus, pcbi->usFmt, pcbi->hszItem, 0L
                 psi->ci.pai, MDPM_FREEHDATA);
         }
         break;
     }
 }



 /*
  * ----------------FRAME SECTION------------------
  *
  * A frame window exists on behalf of every registered thread.  It
  * handles conversation initiation and therefore issues callbacks
  * to the server app as needed to notify or query the server app.
  * The callback queue is always bypassed for these synchronous
  * events.
  */

 /***************************** Private Function ****************************\
 * MRESULT EXPENTRY subframeWndProc(hwnd, msg, mp1, mp2)
 * HWND hwnd;
 * USHORT msg;
 * MPARAM mp1;
 * MPARAM mp2;
 *
 * This routine takes care of setting up server windows as needed to respond
 * to incomming WM_DDE_INTIIATE messages.  It is subclassed from the top
 * level frame of the server application.
 *
 * History:  created 12/20/88    sanfords
 \***************************************************************************/
 MRESULT EXPENTRY subframeWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     PAPPINFO pai;

     pai = GetCurrentAppInfo(FALSE);

     switch (msg) {
     case UM_REGISTER:
     case UM_UNREGISTER:
         /*
          * we pass notification messages through this proc so we can make the
          * xfer call within the correct thread's context.
          */
         MakeCallback(pai, (HCONV)0, (HSZ)0, (HSZ)mp1, 0,
                 msg == UM_REGISTER ? XTYP_REGISTER : XTYP_UNREGISTER,
                 (HDMGDATA)mp2, msg, 0, 0L);
         return(0);
         break;

     case WM_DDE_INITIATE:
         FrameInitConv((HWND)mp1, (PDDEINIT)mp2);
         FreeData((PMYDDES)mp2, pai);
         break;

     default:
         return((*lpfnFrameWndProc)(hwnd, msg, mp1, mp2));
         break;
     }
 }



 void FrameInitConv(hwndClient, pddei)
 HWND hwndClient;
 PDDEINIT pddei;
 {
     PAPPINFO pai, paiClient;
     INITINFO ii;
     HSZPAIR hp[2];
     PHSZPAIR php;
     HSZ hsz = 0;
     HDMGDATA hDataCC;
     PDDESTRUCT pddes;
     HWND hwndServer;
     CONVCONTEXT cc;
     BOOL fWild;

     if (!CheckSel(SELECTOROF(pddei))) {
         AssertF(FALSE, "Invalid DDEINIT selector");
         return;
     }

     SemCheckOut();

     pai = GetCurrentAppInfo(FALSE);
     /*
      * If we are filtering and no app names are registered, quit.
      */
     if ((pai->afCmd & DMGCMD_FILTERINITS) &&
             QPileItemCount(pai->pAppNamePile) == 0)
         return;

     /*
      * filter out inits from ourselves and other agents (if we are an agent)
      */
     if (WinQueryWindowPtr(hwndClient, QWP_PFNWP) == ClientWndProc) {
         paiClient = WinSendMsg(hwndClient, UM_QUERY, (MPARAM)Q_APPINFO, 0L);
         if (paiClient == pai)
             return;

         if ((pai->afCmd & DMGCMD_AGENT) && (paiClient->afCmd & DMGCMD_AGENT))
             return;
     }

     /*
      * make sure ii.pCC is set up right.
      */
     if (pddei->cb >= sizeof(DDEINIT) && pddei->offConvContext) {
         /*
          * new dde init structure!
          */
         ii.pCC = DDEI_PCONVCONTEXT(pddei);
     } else {
         ii.pCC = &cc;
         cc.cb = sizeof(CONVCONTEXT);
         cc.idCountry = syscc.country;
         cc.usCodepage = syscc.codepage;
         cc.fsContext = 0;
     }


     hp[0].hszApp = GetHsz(PSZAPP(pddei), ii.pCC->idCountry,
             ii.pCC->usCodepage, TRUE);

     /*
      * filter out unwanted app names.
      */
     if (hp[0].hszApp && (pai->afCmd & DMGCMD_FILTERINITS) &&
             !FindPileItem(pai->pAppNamePile, CmppHsz, (PBYTE)&hp[0].hszApp, 0
         FreeHsz(hp[0].hszApp);
         return;
     }

     hp[0].hszTopic = GetHsz(PSZTOPIC(pddei), ii.pCC->idCountry,
             ii.pCC->usCodepage, TRUE);

     hp[1].hszApp = hp[1].hszTopic = 0L;

     fWild = (hp[0].hszApp == 0L || hp[0].hszTopic == 0);

     hDataCC = PutData((PBYTE)ii.pCC, (ULONG)sizeof(CONVCONTEXT), 0L, (HSZ)NUL
             0, 0, pai);

     if (hDataCC == NULL)
         goto CheckOut;

     pddes = (PDDESTRUCT)DoCallback(pai, NULL, hp[0].hszTopic,
                     hp[0].hszApp, 0, (fWild ? XTYP_WILDINIT : XTYP_INIT),
                     hDataCC);

     if (pddes == NULL)
         goto CheckOut;

     FindPileItem(pai->pHDataPile, CmpULONG, (PBYTE)&hDataCC, FPI_DELETE);
     DosFreeSeg(SELECTOROF(hDataCC));

     if (fWild) {
         php = (PHSZPAIR)DDES_PABDATA(pddes);
     } else {
         php = &hp[0];
         pddes = NULL;
     }

     /*
      * now php points to a 0 terminated list of hszpairs to respond to.
      */
     SemEnter();
     while (QuerylatomLength((LATOM)php->hszApp) &&
             QuerylatomLength((LATOM)php->hszTopic)) {
         SemLeave();
         if ((hwndServer = CreateServerWindow(pai, php->hszTopic)) == 0)
             break;
         /*
          * have the server respond
          */
         ii.hszAppName = php->hszApp;
         ii.hszTopic = php->hszTopic;
         WinSendMsg(hwndServer, UMSR_INITIATE, (MPARAM)&ii, hwndClient);

         /*
          * confirm initialization to server app
          */
         DoCallback(pai, (HCONV)hwndServer, php->hszTopic, php->hszApp,
                 0, XTYP_INIT_CONFIRM, 0L);

         php++;
         SemEnter();
     }
     SemLeave();
     SemCheckOut();
 CheckOut:
     FreeHsz(hp[0].hszApp);
     FreeHsz(hp[0].hszTopic);
     if (fWild)
         FreeData((PMYDDES)pddes, pai);
 }

 HWND CreateServerWindow(
 PAPPINFO pai,
 HSZ hszTopic)
 {
     HWND hwndTSvr, hwndServer;

     SemCheckOut();
     /*
      * locate or make a Topic server window...
      */
     if ((hwndTSvr =
             HwndFromHsz(hszTopic, pai->pSvrTopicList)) == 0) {
         /*
          * NO - make one.
          */
         if ((hwndTSvr = WinCreateWindow(pai->hwndDmg, SZDEFCLASS, "", 0L,
                 0, 0, 0, 0, (HWND)NULL, HWND_BOTTOM, WID_SVRTOPIC,
                 0L, 0L)) == 0L) {
             pai->LastError = DMGERR_PMWIN_ERROR;
             return(NULL);
         }
         AddHwndHszList(hszTopic, hwndTSvr, pai->pSvrTopicList);
     }

     /*
      * Create the server window
      */
     if ((hwndServer = WinCreateWindow(hwndTSvr, SZSERVERCLASS, "", 0L,
             0, 0, 0, 0, (HWND)NULL, HWND_BOTTOM, WID_SERVER, 0L, 0L)) == 0L)
         pai->LastError = DMGERR_PMWIN_ERROR;
         return(NULL);
     }
     return(hwndServer);
 }

 /*
  * main application window - parent of all others in app.
  *
  * 6/12/90 sanfords     Fixed semaphore bug
  */
 MRESULT EXPENTRY DmgWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
 #define pai ((PAPPINFO)mp1)
     PCBLI pli, pliNext;
     BOOL fException;
     HDMGDATA hDataRet;

     hwnd;
     mp2;

     switch (msg) {
     case UM_CHECKCBQ:
         /*
          * We consider everything to be blocked if we are in a client
          * transfer modal loop.   This prevents recursive timeout
          * calls.
          */
         if (pai->hwndTimer)
             return(0);

         /*
          * This is where we actually do callbacks.  We do them via this
          * window proc so that we can asynchronously institute callbacks
          * via a PostMsg().
          */
         SemCheckOut();
         SemEnter();
         /*
          * process all enabled conversation callbacks.
          */
         for (pli = (PCBLI)pai->plstCB->pItemFirst; pli; pli = (PCBLI)pliNext)
             pliNext = (PCBLI)pli->next;
             fException = FindLstItem(pai->plstCBExceptions, CmpULONG, (PLITEM
                     == NULL ? FALSE : TRUE;
             if (fException == pai->fEnableCB)
                 continue; /* blocked */

             pai->cInCallback++;
             SemLeave();
             /*
              * make the actual callback here.
              */
             hDataRet = DoCallback(pai, pli->hConv, pli->hszTopic,
                     pli->hszItem, pli->usFmt, pli->usType, pli->hDmgData);
             SemEnter();
             if (pai->cInCallback > 0)   /* test incase exlst processing messe
                 pai->cInCallback--;

             /*
              * If the callback resulted in a BLOCK, disable this conversation
              */
             if (hDataRet == CBR_BLOCK && !(pli->usType & XTYPF_NOBLOCK)) {
                 SemLeave();
                 DdeEnableCallback(pli->hConv, FALSE);
                 SemEnter();
                 continue;
             } else {
                 /*
                  * otherwise finish processing the callback.
                  */
                 if (WinIsWindow(DMGHAB, pli->hConvPartner)) {
                     SemLeave();
                     QReply(pli, (PDDESTRUCT)hDataRet);
                     SemEnter();
                 }
                 RemoveLstItem(pai->plstCB, (PLITEM)pli);
             }
         }
         SemLeave();
         return(0);
         break;

     default:
         WinDefWindowProc(hwnd, msg, mp1, mp2);
         break;
     }
 #undef pai
 }


 HDMGDATA DoCallback(
 PAPPINFO pai,
 HCONV hConv,
 HSZ hszTopic,
 HSZ hszItem,
 USHORT usFmt,
 USHORT usType,
 HDMGDATA hDmgData)
 {
     HDMGDATA hDataRet;

     AssertF(IncHszCount(hszTopic) && FreeHsz(hszTopic), "Bad hszTopic on call
     AssertF(IncHszCount(hszItem) && FreeHsz(hszItem), "Bad hszItem on callbac

     if (usType & XCLASS_DATAIN) {
         AssertF(CheckSel(SELECTOROF(hDmgData)), "invalid callback data handle
         ((PMYDDES)hDmgData)->fs |= HDATA_READONLY;
     }

 #ifdef CRUISER
     if (pai->afCmd & DMGCMD_32BIT)
         hDataRet = ThkCallback(hConv, hszTopic, hszItem, usFmt, usType, hDmgD
                 pai->pfnCallback);
     else
 #endif
         hDataRet = (*pai->pfnCallback)(hConv, hszTopic, hszItem, usFmt, usTyp
                 hDmgData);

     if (usType & XCLASS_DATA && CheckSel(SELECTOROF(hDataRet)) > sizeof(MYDDE
             ((PMYDDES)hDataRet)->magic == MYDDESMAGIC) {
         if (((PMYDDES)hDataRet)->pai != pai) {
             AssertF(FALSE, "hData from callback not created by same thread");
             pai->LastError = DMGERR_DLL_USAGE;
             hDataRet = NULL;
         }
     }
     return(hDataRet);
 }




 EA.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\STOCK\EA.C

 /***        ea.c - layer for EA support
  *
  *        Author:
  *            Benjamin W. Slivka
  *            (c) 1990
  *            Microsoft Corporation
  *
  *        History:
  *            08-Feb-1990 bens        Initial version (Subset from EA.EXE sou
  *            02-May-1990 bens        Added SetEAValue (copied from ea.exe)
  *            01-Jun-1990 bens        Support binary EAs
  */

 #define INCL_DOSERRORS
 #define INCL_DOSFILEMGR
 #define INCL_NOPM

 #include <os2.h>

 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <memory.h>

 #include "ea.h"
 #include "mem.h"


 #ifdef CHECKASSERTS
 #define dbg(a)        a
 #else
 #define dbg(a)
 #endif

 // Buffer sizes for EA API calls
 #define CB_GEAL       400                // Enough for one GEA in list
 #define CB_FEAL      2000                // Enough for large file list


 char *            TranslateValue(char *pbValue,USHORT cbValue,USHORT *pcbValu


 /***        EAQueryValue - Get text EA value from file
  *
  *        Entry
  *            pszFile - File path
  *            pszName - EA name
  *            pcbValue - USHORT to receive value length
  *
  *        Exit-Success
  *            returns non-zero pointer to value; Caller must free this!
  *                If value is ASCII
  *                    *pcbValue == 0;
  *                If value is BINARY
  *                    *pcbValue == length of value;
  *
  *        Exit-Failure
  *            returns NULL
  */
 char *EAQueryValue(char *pszFile,char *pszName,USHORT *pcbValue)
 {
     USHORT        cb;
     EAOP        eaop;
     FEA *        pfea;
     FEA *        pfeaEnd;
     FEALIST *        pFEAList;
     GEA *        pgea;
     GEALIST *        pGEAList;
     char *        psz;
     char *        pszValue;
     USHORT        rc;

     //
     // Alloc GEAList and FEAList
     //
     pGEAList = MemAlloc(CB_GEAL);
     if (pGEAList == NULL) {
         return NULL;
     }

     pFEAList = MemAlloc(CB_FEAL);
     if (pFEAList == NULL) {
         MemFree(pGEAList);
         return NULL;
     }

     // Build GEA List with one GEA

     pgea = pGEAList->list;                // Point at first GEA
     cb = strlen(pszName);
     pgea->cbName = (UCHAR)cb;                // Set length
     memcpy(pgea->szName,pszName,cb+1);        // Copy name and NUL
     pgea = (GEA *)((char *)pgea + cb + sizeof(GEA));
     pGEAList->cbList = (char *)pgea - (char *)pGEAList; // Set buffer size

     // Get attribute value

     pFEAList->cbList = CB_FEAL;         // Set size of FEA list
     eaop.fpGEAList = pGEAList;
     eaop.fpFEAList = pFEAList;

     rc = DosQPathInfo(pszFile,                // File path
                       FIL_QUERYEASFROMLIST, // info level
                       (PBYTE)&eaop,        // EAOP structure
                       sizeof(eaop),        // Size of EAOP
                       0L);                // Reserved
     pfea = (FEA *)pFEAList->list;        // Point at FEA

     //        NOTE: DosQPathInfo only fails if there is an inconsistency in
     //              one of its parameters.  It DOES NOT fail if the EA is
     //              not present.  Rather, on a file system that does not
     //              support EAs, it appears to return pFEAList->cbList ==
     //              sizeof(pFEAList->cbList), indicating no FEAs are present.
     //              If the file system *does* support EAs, but the particular
     //              EA is not present, pFEA->cbValue == 0.

     if ((rc == 0) &&                        // Call succeeded,...
         ((pFEAList->cbList) > sizeof(pFEAList->cbList)) && // FEA is there,..
         (pfea->cbValue > 0)) {                // and file has EA value!
         // Parse EA value
         cb = pfea->cbName;
         psz = (char *)pfea + sizeof(FEA); // Point at name
         pszValue = psz + cb + 1;        // Point at value
         psz = TranslateValue(pszValue,pfea->cbValue,pcbValue);
     }
     else
        psz = NULL;                        // EA not present, or too big

     MemFree(pFEAList);
     MemFree(pGEAList);
     return psz;
 }


 /***        TranslateValue - produce printable representation of EA value
  *
  *        Entry
  *            pbValue  - Value buffer
  *            cbValue  - Length of value buffer
  *            pcbValue - USHORT to receive actual value length
  *
  *        Exit-Success
  *            Returns non-zero pointer to value; caller MUST free!
  *                If value is ASCII
  *                    *pcbValue == 0;
  *                If value is BINARY
  *                    *pcbValue == length of value;
  *
  *        Exit-Failure
  *            Returns NULL
  *
  *
  *  EAT_MVMT - Multi-value, Multi-type
  *
  *        +------+----------+-------+------+--------+-------+---+---+---+---+
  *        | Type | Codepage | Count | Type | Length | Value |...| T | L | V |
  *        +------+----------+-------+------+--------+-------+---+---+---+---+
  *           us            us             us      us      us       ?
  *        \________________________/ \_____________________/     \_________/
  *           MVMT header                          Value 1                 Val
  *
  */
 char * TranslateValue(char *pbValue,USHORT cbValue,USHORT *pcbValue)
 {
     USHORT cb=cbValue;
     USHORT codePage;
     USHORT cValue;
     char * pbDst;
     char * pbSrc;
     char * pszNew;
     USHORT type;

     // Parse MVMT header, if present

     pbSrc = pbValue;

     type = *(USHORT *)pbSrc;                // Get EA value type
     if (type == EAT_MVMT) {
         pbSrc += sizeof(USHORT);        // Skip type
         codePage = *((USHORT*)pbSrc)++; // Get code page
         cValue = *((USHORT*)pbSrc)++;        // Get count of values
         if (cValue != 1)                // Not exactly one value
             return NULL;                //  Fail
         type = *(USHORT *)pbSrc;        // Get EA value type
     }


     // Parse value

     if ( (type == EAT_ASCII) || (type == EAT_BINARY) ) {
         pbSrc += sizeof(USHORT);        // Skip type
         cb = *((USHORT *)pbSrc)++;        // Get data length

         // Allocate buffer for data

         pszNew = MemAlloc(cb+1);        // Leave room for NUL, in ASCII case
         if (pszNew == NULL)
             return NULL;
         pbDst = pszNew;

         // Copy data

         memcpy(pbDst,pbSrc,cb);         // Copy value
         pbDst += cb;                        // Advance destination pointer

         if (type == EAT_ASCII) {
             *pbDst++ = '\0';                // Terminate ASCIIZ string
             *pcbValue = 0;                // Indicate value is ASCIIZ
         }
         else
             *pcbValue = cb;                // Indicate value is binary
         return pszNew;                        // Return value
     }
     else
         return NULL;                        //  Fail
 }


 /***        EASetValue - Create/Change/Delete an EA
  *
  *        Entry
  *            pszFile  - file path
  *            pszName  - EA name
  *            cbValue  - EA length; 0 => pszValue is ASCIIZ
  *            pszValue - EA value; NULL to delete EA
  *
  *        Exit-Success
  *            returns TRUE
  *
  *        Exit-Failure
  *            returns FALSE
  *
  */
 BOOL EASetValue(char *pszFile,char *pszName,USHORT cbValue,char *pszValue)
 {
     USHORT        cbName;
     EAOP        eaop;
     FEA *        pfea;
     FEALIST *        pFEAList;
     char *        psz;
     USHORT        rc;
     USHORT        type;

     // Determine operation

     if (pszValue == NULL) {                // Delete this EA
         type = EAT_ASCII;
         cbValue = 0;
     }
     else if (cbValue == 0) {                // Create/Change value
         type = EAT_ASCII;
         cbValue = strlen(pszValue);        // Compute length (do not count NU
     }
     else {                                // Create/Change Binary value
         type = EAT_BINARY;
     }

     //
     // Alloc FEA List
     //
     pFEAList = MemAlloc(CB_FEAL);
     if (pFEAList == NULL)
         return FALSE;

     cbName = strlen(pszName);

     //
     // Build EA structure
     //
     pfea = (FEA *)pFEAList->list;        // Point at first FEA
     pfea->fEA = 0;                        // No flag settings
     pfea->cbName = (UCHAR)cbName;        // Set name length
     pfea->cbValue = cbValue;                // Set value length

     psz = (char *)pfea + sizeof(FEA);        // Point at location for name
     memcpy(psz,pszName,cbName+1);        // Copy Name *and* NUL
     psz += cbName+1;                        // Point at location for value
     if (cbValue > 0) {                        // Edit/Create EA
         *((USHORT *)psz)++ = EAT_MVMT;        // Set MVMT type (to record cod
         *((USHORT *)psz)++ = NULL;        // Set codepage
         *((USHORT *)psz)++ = 1;         // Only one TLV record
         *((USHORT *)psz)++ = type;        // Set EA type
         *((USHORT *)psz)++ = cbValue;        // Set ASCII length

         pfea->cbValue += 5*sizeof(USHORT); // MVMT header and type and length
         memcpy(psz,pszValue,cbValue);        // Copy Value
     }
     pfea = (FEA *)(psz + cbValue);        // Point at byte after FEA

     //
     // Set size of FEA List (only one FEA)
     //

     pFEAList->cbList = (char *)pfea - (char *)pFEAList;

     eaop.fpGEAList = NULL;
     eaop.fpFEAList = pFEAList;

     rc = DosSetPathInfo(pszFile,            // File path
                         FIL_QUERYEASIZE,    // Set EA
                         (PBYTE)&eaop,            // EAOP structure
                         sizeof(eaop),            // Size of EAOP
                         0,                    // Options
                         0L);                    // Reserved
     MemFree(pFEAList);
     return (rc == 0);
 }


 EDPLINE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\EDPLINE\EDPLINE.C

 /*
     edpline.c -- polyline editor, for practice in mouse handling
     Created by Microsoft Corporation, 1989
 */
 #define INCL_DOSMEMMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINMESSAGEMGR
 #define INCL_WINSWITCHLIST
 #define INCL_WINDIALOGS
 #define INCL_GPIBITMAPS
 #define INCL_GPIPRIMITIVES
 #define INCL_WINMENUS
 #define        INCL_WININPUT
 #define        INCL_WINFRAMEMGR
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>

 #include "edpline.h"



 #define        abs(x)                        (((x) > 0) ? (x) : -(x))
 #define PRIM_POLYLINE                0x0001
 #define PRIM_POLYFILLET         0x0002
 #define PRIM_POLYSPLINE         0x0004
 #define PRIM_POINTARC                0x0008


 /************************************************************************
 *
 *   Function declarations
 *
 ************************************************************************/

 /* Private functions */

 VOID   cdecl main(VOID);
 BOOL   InitGlobals(VOID);
 BOOL   InitApp(VOID);
 VOID   Close(HWND);
 VOID   Command(HWND, USHORT);
 VOID   Paint(HPS, BOOL);
 VOID   MouseMove(HWND, MPARAM);
 VOID   ButtonUp(HWND, USHORT);
 VOID   ButtonDown(HWND, USHORT, MPARAM);
 USHORT IsPtInList(PPOINTL);
 USHORT AddPtToList(PPOINTL);
 BOOL   IsPtCloseToLine(PPOINTL, PPOINTL, PPOINTL);
 VOID   DrawPrimitive(HPS, USHORT);
 VOID   DrawPolyLine(HPS);
 VOID   DrawPolyFillet(HPS);
 VOID   DrawPolySpline(HPS);
 VOID   DrawPointArc(HPS);
 VOID   DrawControlPoints(HPS, LONG, PPOINTL);
 VOID   MyMessageBox(HWND, PSZ);
 VOID   SwapLong(PLONG, PLONG);

 /* Exported functions */

 ULONG        EXPENTRY WndProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY AboutDlgProc(HWND, USHORT, MPARAM, MPARAM);



 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 typedef struct
 {
     HAB  hab;
     HMQ  hMsgQ;
     HWND hwndFrame;
     HWND hwnd;

     ULONG   flPrim;
     BOOL    fDisplayControlPoints;
     LONG    cptl;
     PPOINTL pptl;

     USHORT  usPtGrabbed;
     BOOL    fDragging;

     ULONG   ulHitPrecision;

 } GLOBALDATA;
 GLOBALDATA global;




 /************************************************************************
 *
 *   main
 *
 *   WinInitialize resizes our ring 2 stack, among other things, so
 *   we won't GP fault trying to do graphics.  WinCreateMsgQueue defines
 *   us as a REAL PM app. (as well as the WINDOWAPI statement in the .DEF
 *   file...)   Call a sub to register our window class and create a window.
 *   Loop over messages.  Exit cleanly.
 *
 ************************************************************************/

 VOID cdecl
 main()
 {
     QMSG qMsg;
     int iRet = 0;


     global.hab         = WinInitialize(0);
     global.hMsgQ = WinCreateMsgQueue(global.hab, 0);

     if (InitApp())
         while (WinGetMsg( global.hab, (PQMSG)&qMsg, (HWND)NULL, 0, 0 ))
             WinDispatchMsg( global.hab, (PQMSG)&qMsg );
     else
         iRet = -1;

     WinDestroyWindow( global.hwndFrame );
     WinDestroyMsgQueue( global.hMsgQ );
     WinTerminate( global.hab );
     DosExit(EXIT_PROCESS, iRet);
 }




 /****************************************************************************
 *
 *   InitGlobals
 *
 *   Initialize global variables.
 *
 ****************************************************************************/

 BOOL
 InitGlobals()
 {
     global.flPrim = PRIM_POLYLINE;
     global.fDisplayControlPoints = TRUE;

     global.cptl = 0L;
     global.pptl = NULL;
     if (DosAllocSeg(CPTLMAX * sizeof(POINTL),
                    ((PUSHORT)&global.pptl)+1, 0))
         return FALSE;

     global.usPtGrabbed = -1;
     global.fDragging = FALSE;
     global.ulHitPrecision = 0L;

     return TRUE;
 }




 /****************************************************************************
 *
 *   InitApp
 *
 *   Register application window class and creates standard window.
 *
 ****************************************************************************/

 #define INIT_MENU_ITEM(val, var)     \
         TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var))

 BOOL
 InitApp()
 {
     char szTitle[24];
     ULONG ctldata;
     PID pid;
     TID tid;
     HSWITCH hsw;
     static SWCNTRL swctl = { 0, 0, 0, 0, 0, SWL_VISIBLE,
                              SWL_JUMPABLE, "Edit Polyline", 0 };

     if (!InitGlobals())
         return FALSE;


     /*  Register Application Window Class  */

     WinLoadString( global.hab, (HMODULE) NULL, IDS_TITLE, sizeof(szTitle), (P
     if ( !WinRegisterClass( global.hab, (PCH)szTitle, (PFNWP)WndProc,
             CS_SIZEREDRAW, 0 ))
         return FALSE;



     /* Create a window instance of class "PolyLine Editor" */

     ctldata = FCF_STANDARD &
      ~(ULONG)(FCF_ICON | FCF_ACCELTABLE | FCF_TASKLIST);

     if (global.hwndFrame = WinCreateStdWindow(
         HWND_DESKTOP,                   /* specify desktop as parent window
         WS_VISIBLE,                   /* window styles
         &ctldata,                   /* frame creation flags
         (PCH)szTitle,                   /* window class name
         (PCH)szTitle,                   /* name appearing in window caption
         0L,                           /*
         (HMODULE)NULL,                   /* use current executable module id
         IDR_EDPLINE,                   /* menu id
         (HWND FAR *)&global.hwnd   /* window handle
         ))
     {
         INIT_MENU_ITEM(IDM_CTLPOINTS, global.fDisplayControlPoints);

         if (global.flPrim & PRIM_POLYLINE)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYLINE);
         if (global.flPrim & PRIM_POLYFILLET)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYFILLET);
         if (global.flPrim & PRIM_POLYSPLINE)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYSPLINE);
         if (global.flPrim & PRIM_POINTARC)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POINTARC);


         /* Add ourselves to the switch list. */

         WinQueryWindowProcess(global.hwndFrame, &pid, &tid);
         swctl.hwnd        = global.hwndFrame;
         swctl.idProcess = pid;
         hsw = WinAddSwitchEntry(&swctl);

         return TRUE;
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   WndProc
 *
 *   Process messages for the window class.
 *
 ************************************************************************/

 ULONG EXPENTRY
 WndProc( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     HPS   hps;


     switch (usMsg)
     {
     case WM_CLOSE:
         Close(hwnd);
         break;

     case WM_COMMAND:
         Command(hwnd, LOUSHORT(mp1));
         break;

     case WM_PAINT:
         hps = WinBeginPaint(global.hwnd, NULL, NULL);
         if (global.ulHitPrecision == 0L)
         {
             HDC hdc;
             LONG cx;

             if (hdc = WinQueryWindowDC(global.hwnd)) {
                 DevQueryCaps(hdc, CAPS_MARKER_WIDTH,  1L,  &cx);
                 global.ulHitPrecision = (cx >> 17) + 1L;
             } else {
                 global.ulHitPrecision = 6L;
             }
         }
         Paint(hps, TRUE);
         WinEndPaint(hps);
         break;

     case WM_BUTTON1DOWN:
     case WM_BUTTON2DOWN:
         ButtonDown(hwnd, usMsg, mp1);
         break;

     case WM_BUTTON1UP:
     case WM_BUTTON2UP:
         ButtonUp(hwnd, usMsg);
         break;

     case WM_MOUSEMOVE:
         MouseMove(hwnd, mp1);
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;

     default:
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   MouseMove
 *
 ************************************************************************/

 VOID
 MouseMove(hwnd, mp1)
 HWND hwnd;
 MPARAM mp1;
 {
     POINTL ptl;
     HPS hps;

     if (hwnd == global.hwnd)
         if (global.fDragging)
         {
             ptl.x = (LONG) LOUSHORT(mp1);
             ptl.y = (LONG) HIUSHORT(mp1);

             if (global.usPtGrabbed != -1)
             {
                 hps = WinGetPS(hwnd);
                 Paint(hps, FALSE);

                 *(global.pptl+global.usPtGrabbed) = ptl;

                 Paint(hps, FALSE);
                 WinReleasePS(hps);
             }
         }
 }




 /************************************************************************
 *
 *   ButtonUp
 *
 ************************************************************************/

 VOID
 ButtonUp(hwnd, usMsg)
 HWND hwnd;
 USHORT usMsg;
 {
     int i;
     HPS hps;


     if (hwnd == global.hwnd)
         if (global.fDragging)
         {
             global.fDragging = FALSE;
             if (global.usPtGrabbed != -1)
             {
                 if (usMsg == WM_BUTTON2UP)
                 {
                     hps = WinGetPS(hwnd);
                     Paint(hps, FALSE);

                     if ((i = global.usPtGrabbed) < (int) global.cptl-1)
                         while (i < (int) global.cptl-1)
                         {
                             global.pptl[i] = global.pptl[i+1];
                             ++i;
                         }

                     --global.cptl;
                     global.usPtGrabbed = -1;

                     Paint(hps, FALSE);
                     WinReleasePS(hps);
                 }
                 else        /* WM_BUTTON1UP */
                     global.usPtGrabbed = -1;
             }
         }
 }




 /************************************************************************
 *
 *   ButtonDown
 *
 ************************************************************************/

 VOID
 ButtonDown(hwnd, usMsg, mp1)
 HWND hwnd;
 USHORT usMsg;
 MPARAM mp1;
 {
     POINTL ptl;
     HPS hps;
     USHORT usNewPtGrabbed;


     if (hwnd == global.hwnd)
         if (!global.fDragging)
         {
             global.fDragging = TRUE;

             ptl.x = (LONG) LOUSHORT(mp1);
             ptl.y = (LONG) HIUSHORT(mp1);

             if ((usNewPtGrabbed = IsPtInList(&ptl)) != -1)
                 global.usPtGrabbed = usNewPtGrabbed;

             if (usMsg == WM_BUTTON1DOWN)
             {
                 hps = WinGetPS(hwnd);
                 Paint(hps, FALSE);

                 if (usNewPtGrabbed == -1)
                     global.usPtGrabbed = AddPtToList(&ptl);
                 else
                     global.usPtGrabbed = usNewPtGrabbed;

                 Paint(hps, FALSE);
                 WinReleasePS(hps);

                 if (global.usPtGrabbed == -1)
                     MyMessageBox(global.hwnd, "Cannot add any more points.");
             }
         }
 }




 /************************************************************************
 *
 *   IsPtInList
 *
 ************************************************************************/

 USHORT
 IsPtInList(pptl)
 PPOINTL pptl;
 {
     int i;


     /* try to find pptl in the points we already have */
     for (i = 0; i < (int) global.cptl; ++i)
         if (((abs(pptl->x - global.pptl[i].x))
                 <= (LONG) global.ulHitPrecision)
          && ((abs(pptl->y - global.pptl[i].y))
                 <= (LONG) global.ulHitPrecision))
                 return i;

     /* couldn't find it */
     return -1;
 }




 /************************************************************************
 *
 *   AddPtToList
 *
 ************************************************************************/

 USHORT
 AddPtToList(pptl)
 PPOINTL pptl;
 {
     int i, j;

     if (global.cptl < CPTLMAX)
     {
         /* check for new points lying on a line segment */
         for (i = 0; i < (int) (global.cptl - 1L); ++i)
             if (IsPtCloseToLine(&global.pptl[i], &global.pptl[i+1], pptl))
             {
                 for (j = (int) global.cptl; j > i+1; --j)
                     global.pptl[j] = global.pptl[j - 1];
                 global.pptl[i+1] = *pptl;
                 ++global.cptl;
                 return i+1;
             }

         /* append the point */

         i = (int) global.cptl;
         global.pptl[i] = *pptl;
         ++global.cptl;
         return i;
     }

     return -1;
 }




 /************************************************************************
 *
 *   IsPtCloseToLine
 *
 ************************************************************************/

 BOOL
 IsPtCloseToLine(pptl1, pptl2, pptlTest)
 PPOINTL pptl1;
 PPOINTL pptl2;
 PPOINTL pptlTest;
 {
     POINTL ptlLL, ptlUR;
     LONG dx, dy, yIntercept, result;


     /* find the bounding box of the line segment */

     ptlLL = *pptl1;        /* assume line goes lower left to upper right */
     ptlUR = *pptl2;
     if (pptl1->x > pptl2->x)
         SwapLong(&ptlLL.x, &ptlUR.x);
     if (pptl1->y > pptl2->y)
         SwapLong(&ptlLL.y, &ptlUR.y);


     /* adjust the bounding box if it's too narrow */

     dx = pptl2->x - pptl1->x;
     if (abs(dx) <= (LONG) (global.ulHitPrecision >> 1))
     {
         ptlLL.x -= (LONG) (global.ulHitPrecision >> 1);
         ptlUR.x += (LONG) (global.ulHitPrecision >> 1);
     }
     dy = pptl2->y - pptl1->y;
     if (abs(dy) <= (LONG) (global.ulHitPrecision >> 1))
     {
         ptlLL.y -= (LONG) (global.ulHitPrecision >> 1);
         ptlUR.y += (LONG) (global.ulHitPrecision >> 1);
     }


     /* see if the test point is in the bounding box of the line segment */

     if ((pptlTest->x >= ptlLL.x) &&
         (pptlTest->x <= ptlUR.x) &&
         (pptlTest->y >= ptlLL.y) &&
         (pptlTest->y <= ptlUR.y))
     {
         /* test for special cases */

         if (dx == 0)
         {
             if (abs(pptlTest->x - pptl1->x) <= (LONG) global.ulHitPrecision)
                 return TRUE;
             else
                 return FALSE;
         }

         if (dy == 0)
         {
             if (abs(pptlTest->y - pptl1->y) <= (LONG) global.ulHitPrecision)
                 return TRUE;
             else
                 return FALSE;
         }


         /* test for general case */

         yIntercept = pptl1->y - (pptl1->x * dy) / dx;

         result = pptlTest->y - (pptlTest->x * dy / dx) - yIntercept;
         if (abs(result) <= (LONG) global.ulHitPrecision)
             return TRUE;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   SwapLong
 *
 ************************************************************************/

 VOID
 SwapLong(pl1, pl2)
 PLONG pl1, pl2;
 {
     LONG lTmp;

     lTmp = *pl1;
     *pl1 = *pl2;
     *pl2 = lTmp;
 }




 /************************************************************************
 *
 *   Close
 *
 ************************************************************************/

 VOID
 Close(hwnd)
 HWND hwnd;
 {
     WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
 }




 /************************************************************************
 *
 *   Command
 *
 *   Dispatches menu commands to the proper handlers.
 *
 ************************************************************************/

 #define UPDATE_MENU_BOOL(var, val)                                \
         {                                                        \
             TOGGLE_BOOL((var));                                 \
             TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var));        \
         }

 #define UPDATE_MENU_LIST(var, val)                                \
         {                                                        \
             UNCHECK_MENU_ITEM(global.hwndFrame, (var));         \
             (var) = (val);                                        \
             CHECK_MENU_ITEM(global.hwndFrame, (var));                \
         }

 VOID
 Command(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     HPS hps;
     BOOL fRedraw = FALSE;
     int rc;

     switch (id)
     {
     case IDM_ABOUT:
         rc = WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc, (HMODULE) NULL, IDD_
         fRedraw = FALSE;
         break;

     case IDM_NOPRIM:
         global.flPrim = 0L;
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYLINE, 0);
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYFILLET, 0);
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYSPLINE, 0);
         fRedraw = TRUE;
         break;

     case IDM_POLYLINE:
         global.flPrim ^= PRIM_POLYLINE;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYLINE
         fRedraw = TRUE;
         break;

     case IDM_POLYFILLET:
         global.flPrim ^= PRIM_POLYFILLET;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYFILL
         fRedraw = TRUE;
         break;

     case IDM_POLYSPLINE:
         global.flPrim ^= PRIM_POLYSPLINE;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYSPLI
         fRedraw = TRUE;
         break;

     case IDM_POINTARC:
         global.flPrim ^= PRIM_POINTARC;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POINTARC
         fRedraw = TRUE;
         break;

     case IDM_CTLPOINTS:
         UPDATE_MENU_BOOL(global.fDisplayControlPoints, IDM_CTLPOINTS);
         fRedraw = TRUE;
         break;

     case IDM_CLEARALL:
         global.cptl = 0L;
         fRedraw = TRUE;
         break;
     }

     if (fRedraw)
     {
         hps = WinGetPS(hwnd);
         Paint(hps, TRUE);
         WinReleasePS(hps);
     }
 }




 /************************************************************************
 *
 *   Paint
 *
 ************************************************************************/

 VOID
 Paint(hps, fClearScreen)
 HPS  hps;
 BOOL fClearScreen;
 {
     LINEBUNDLE lb;
     RECTL rcl;



     if (fClearScreen)
     {
         /* clear the screen */
         WinQueryWindowRect(global.hwnd, &rcl);
         GpiBitBlt(hps, NULL, 2L, (PPOINTL) &rcl, ROP_ONE, 0L);
     }


     if (global.cptl > 0L)
     {
         if (global.fDisplayControlPoints)
         {
             if (fClearScreen)
                 /* draw all the control points */
                 DrawControlPoints(hps, global.cptl, global.pptl);
             else if (global.usPtGrabbed != -1)
                 /* draw just the control point that moved */
                 DrawControlPoints(hps, 1L, global.pptl+global.usPtGrabbed);
         }

         /* set mix mode to xor */
         lb.usMixMode = FM_XOR;
         GpiSetAttrs(hps, PRIM_LINE, LBB_MIX_MODE, 0L, &lb);

         /* draw the current primitives */

         if (global.flPrim & PRIM_POLYLINE)
         {
             lb.lColor = CLR_BROWN;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYLINE);
         }

         if (global.flPrim & PRIM_POLYFILLET)
         {
             lb.lColor = CLR_DARKCYAN;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYFILLET);
         }

         if (global.flPrim & PRIM_POLYSPLINE)
         {
             lb.lColor = CLR_DARKPINK;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYSPLINE);
         }

         if (global.flPrim & PRIM_POINTARC)
         {
             lb.lColor = CLR_BACKGROUND;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POINTARC);
         }
     }
 }




 /************************************************************************
 *
 *   DrawPrimitive
 *
 ************************************************************************/

 VOID
 DrawPrimitive(hps, usPrim)
 HPS hps;
 USHORT usPrim;
 {
     switch ( usPrim )
     {
     case IDM_POLYLINE:
         DrawPolyLine(hps);
         break;

     case IDM_POLYFILLET:
         DrawPolyFillet(hps);
         break;

     case IDM_POLYSPLINE:
         DrawPolySpline(hps);
         break;

     case IDM_POINTARC:
         DrawPointArc(hps);
         break;
     }
 }




 /************************************************************************
 *
 *   DrawPolyLine
 *
 ************************************************************************/

 VOID
 DrawPolyLine(hps)
 HPS hps;
 {
     GpiSetCurrentPosition( hps, global.pptl );
     GpiPolyLine( hps, global.cptl-1L, global.pptl+1 );
 }




 /************************************************************************
 *
 *   DrawPolyFillet
 *
 ************************************************************************/

 VOID
 DrawPolyFillet(hps)
 HPS hps;
 {
     if (global.cptl > 2)
     {
         GpiSetCurrentPosition( hps, global.pptl );
         GpiPolyFillet( hps, global.cptl-1L, global.pptl+1 );
     }
 }




 /************************************************************************
 *
 *   DrawPolySpline
 *
 ************************************************************************/

 VOID
 DrawPolySpline(hps)
 HPS hps;
 {
     USHORT cptSlack;        /* # points in pptl not usable by PolySpline */

     /* GpiPolySpline expects the number of points to be a
        multiple of 3.  If we have a non-multiple of three,
        (excluding the first point, which we've used to set
        the current position), only pass the largest multiple
        of three, saving the rest for the next go-round. */

     cptSlack = (int)((global.cptl-1L) % 3) + 1;
     GpiSetCurrentPosition( hps, global.pptl );
     GpiPolySpline( hps, global.cptl-cptSlack,
                    global.pptl+1 );
 }




 /************************************************************************
 *
 *   DrawPointArc
 *
 ************************************************************************/

 VOID
 DrawPointArc(hps)
 HPS hps;
 {
     if (global.cptl >= 3L)
     {
         GpiSetCurrentPosition( hps, global.pptl );
         GpiPointArc( hps, global.pptl+1 );
     }
 }




 /************************************************************************
 *
 *   DrawControlPoints
 *
 ************************************************************************/

 VOID
 DrawControlPoints(hps, cptl, pptl)
 HPS hps;
 LONG cptl;
 PPOINTL pptl;
 {
     MARKERBUNDLE mb;

     mb.lColor = CLR_TRUE;
     mb.usMixMode = FM_XOR;
     GpiSetAttrs(hps, PRIM_MARKER, MBB_COLOR | MBB_MIX_MODE, 0L, &mb);

     GpiPolyMarker(hps, cptl, pptl);
 }




 /************************************************************************
 *
 *   MyMessageBox
 *
 *   Displays a message box with the given string.  To simplify matters,
 *   the box will always have the same title ("PolyLine Editor"), will always
 *   have a single button ("Ok"), will always have an exclamation point
 *   icon, and will always be application modal.
 *
 ************************************************************************/

 VOID
 MyMessageBox(hWnd, sz)
 HWND hWnd;
 PSZ sz;
 {
     static char *szTitle = "PolyLine Editor";

     WinMessageBox(HWND_DESKTOP, hWnd, sz, szTitle, (HMODULE) NULL,
                   MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
 }

 MRESULT EXPENTRY AboutDlgProc(hDlg, msg, mp1, mp2)
 /*
     About... dialog procedure
 */
 HWND        hDlg;
 USHORT        msg;
 MPARAM        mp1;
 MPARAM        mp2;
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 EDPLINE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLIPVIEW\EDPLINE\EDPLINE.C

 /*
     edpline.c -- polyline editor, for practice in mouse handling
     Created by Microsoft Corporation, 1989
 */
 #define INCL_DOSMEMMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINMESSAGEMGR
 #define INCL_WINSWITCHLIST
 #define INCL_WINDIALOGS
 #define INCL_GPIBITMAPS
 #define INCL_GPIPRIMITIVES
 #define        INCL_GPITRANSFORMS
 #define INCL_WINMENUS
 #define        INCL_WININPUT
 #define        INCL_WINFRAMEMGR
 #define        INCL_WINCLIPBOARD
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>

 #include "edpline.h"



 #define        abs(x)                        (((x) > 0) ? (x) : -(x))
 #define PRIM_POLYLINE                0x0001
 #define PRIM_POLYFILLET         0x0002
 #define PRIM_POLYSPLINE         0x0004
 #define PRIM_POINTARC                0x0008


 /************************************************************************
 *
 *   Function declarations
 *
 ************************************************************************/

 /* Private functions */

 VOID   cdecl main(VOID);
 BOOL   InitGlobals(VOID);
 BOOL   InitApp(VOID);
 VOID   Close(HWND);
 VOID   Command(HWND, USHORT);
 VOID   Paint(HPS, BOOL);
 VOID   MouseMove(HWND, MPARAM);
 VOID   ButtonUp(HWND, USHORT);
 VOID   ButtonDown(HWND, USHORT, MPARAM);
 USHORT IsPtInList(PPOINTL);
 USHORT AddPtToList(PPOINTL);
 BOOL   IsPtCloseToLine(PPOINTL, PPOINTL, PPOINTL);
 VOID   DrawPrimitive(HPS, USHORT);
 VOID   DrawPolyLine(HPS);
 VOID   DrawPolyFillet(HPS);
 VOID   DrawPolySpline(HPS);
 VOID   DrawPointArc(HPS);
 VOID   DrawControlPoints(HPS, LONG, PPOINTL);
 VOID   MyMessageBox(HWND, PSZ);
 VOID   SwapLong(PLONG, PLONG);

 /* Exported functions */

 ULONG        CALLBACK WndProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT CALLBACK AboutDlgProc(HWND, USHORT, MPARAM, MPARAM);



 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 typedef struct
 {
     HAB  hab;
     HMQ  hMsgQ;
     HWND hwndFrame;
     HWND hwnd;

     ULONG   flPrim;
     BOOL    fDisplayControlPoints;
     LONG    cptl;
     PPOINTL pptl;

     USHORT  usPtGrabbed;
     BOOL    fDragging;

     ULONG   ulHitPrecision;

     HPS     hpsMetafile;
     HDC     hdcMetafile;
     ULONG   hItem;
     SIZEL   sizlPage;
     DEVOPENSTRUC dop;

 } GLOBALDATA;
 GLOBALDATA global;


 /************************************************************************
 *
 *   main
 *
 *   WinInitialize resizes our ring 2 stack, among other things, so
 *   we won't GP fault trying to do graphics.  WinCreateMsgQueue defines
 *   us as a REAL PM app. (as well as the WINDOWAPI statement in the .DEF
 *   file...)   Call a sub to register our window class and create a window.
 *   Loop over messages.  Exit cleanly.
 *
 ************************************************************************/

 VOID cdecl
 main()
 {
     QMSG qMsg;
     int iRet = 0;


     global.hab         = WinInitialize(0);
     global.hMsgQ = WinCreateMsgQueue(global.hab, 0);

     if (InitApp())
         while (WinGetMsg( global.hab, (PQMSG)&qMsg, (HWND)NULL, 0, 0 ))
             WinDispatchMsg( global.hab, (PQMSG)&qMsg );
     else
         iRet = -1;

     WinDestroyWindow( global.hwndFrame );
     WinDestroyMsgQueue( global.hMsgQ );
     WinTerminate( global.hab );
     DosExit(EXIT_PROCESS, iRet);
 }




 /****************************************************************************
 *
 *   InitGlobals
 *
 *   Initialize global variables.
 *
 ****************************************************************************/

 BOOL
 InitGlobals()
 {
     global.flPrim = PRIM_POLYLINE;
     global.fDisplayControlPoints = TRUE;

     global.cptl = 0L;
     global.pptl = NULL;
     if (DosAllocSeg(CPTLMAX * sizeof(POINTL),
                    ((PUSHORT)&global.pptl)+1, 0))
         return FALSE;

     global.usPtGrabbed = -1;
     global.fDragging = FALSE;
     global.ulHitPrecision = 0L;

     return TRUE;
 }




 /****************************************************************************
 *
 *   InitApp
 *
 *   Register application window class and creates standard window.
 *
 ****************************************************************************/

 #define INIT_MENU_ITEM(val, var)     \
         TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var))

 BOOL
 InitApp()
 {
     char szTitle[24];
     ULONG ctldata;
     PID pid;
     TID tid;
     HSWITCH hsw;
     static SWCNTRL swctl = { 0, 0, 0, 0, 0, SWL_VISIBLE,
                              SWL_JUMPABLE, "Edit Polyline", 0 };

     if (!InitGlobals())
         return FALSE;


     /*  Register Application Window Class  */

     WinLoadString( global.hab, (HMODULE) NULL, IDS_TITLE, sizeof(szTitle), (P
     if ( !WinRegisterClass( global.hab, (PCH)szTitle, (PFNWP)WndProc,
             CS_SIZEREDRAW, 0 ))
         return FALSE;



     /* Create a window instance of class "PolyLine Editor" */

     ctldata = FCF_STANDARD &
      ~(ULONG)(FCF_ICON | FCF_TASKLIST);

     if (global.hwndFrame = WinCreateStdWindow(
         HWND_DESKTOP,                   /* specify desktop as parent window
         WS_VISIBLE,                   /* window styles
         &ctldata,                   /* frame creation flags
         (PCH)szTitle,                   /* window class name
         (PCH)szTitle,                   /* name appearing in window caption
         0L,                           /*
         (HMODULE)NULL,                   /* use current executable module id
         IDR_EDPLINE,                   /* menu id
         (HWND FAR *)&global.hwnd   /* window handle
         ))
     {
         INIT_MENU_ITEM(IDM_CTLPOINTS, global.fDisplayControlPoints);

         if (global.flPrim & PRIM_POLYLINE)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYLINE);
         if (global.flPrim & PRIM_POLYFILLET)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYFILLET);
         if (global.flPrim & PRIM_POLYSPLINE)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POLYSPLINE);
         if (global.flPrim & PRIM_POINTARC)
             CHECK_MENU_ITEM(global.hwndFrame, IDM_POINTARC);


         /* Add ourselves to the switch list. */

         WinQueryWindowProcess(global.hwndFrame, &pid, &tid);
         swctl.hwnd        = global.hwndFrame;
         swctl.idProcess = pid;
         hsw = WinAddSwitchEntry(&swctl);

         return TRUE;
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   WndProc
 *
 *   Process messages for the window class.
 *
 ************************************************************************/

 ULONG CALLBACK
 WndProc( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     HPS   hps;

     switch (usMsg)
     {
     case WM_CLOSE:
         Close(hwnd);
         break;

     case WM_COMMAND:
         Command(hwnd, LOUSHORT(mp1));
         break;

     case WM_PAINT:
         hps = WinBeginPaint(global.hwnd, NULL, NULL);

         if (global.ulHitPrecision == 0L)
         {
             HDC hdc;
             LONG cx;

             if (hdc = WinQueryWindowDC(global.hwnd)) {
                 DevQueryCaps(hdc, CAPS_MARKER_WIDTH,  1L,  &cx);
                 global.ulHitPrecision = (cx >> 17) + 1L;
             } else {
                 global.ulHitPrecision = 6L;
             }
         }
         Paint(hps, TRUE);
         WinEndPaint(hps);
         break;

     case WM_BUTTON1DOWN:
     case WM_BUTTON2DOWN:
         ButtonDown(hwnd, usMsg, mp1);
         break;

     case WM_BUTTON1UP:
     case WM_BUTTON2UP:
         ButtonUp(hwnd, usMsg);
         break;

     case WM_MOUSEMOVE:
         MouseMove(hwnd, mp1);
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;

     default:
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   MouseMove
 *
 ************************************************************************/

 VOID
 MouseMove(hwnd, mp1)
 HWND hwnd;
 MPARAM mp1;
 {
     POINTL ptl;
     HPS hps;

     if (hwnd == global.hwnd)
         if (global.fDragging)
         {
             ptl.x = (LONG) LOUSHORT(mp1);
             ptl.y = (LONG) HIUSHORT(mp1);

             if (global.usPtGrabbed != -1)
             {
                 hps = WinGetPS(hwnd);
                 Paint(hps, FALSE);

                 *(global.pptl+global.usPtGrabbed) = ptl;

                 Paint(hps, FALSE);
                 WinReleasePS(hps);
             }
         }
 }




 /************************************************************************
 *
 *   ButtonUp
 *
 ************************************************************************/

 VOID
 ButtonUp(hwnd, usMsg)
 HWND hwnd;
 USHORT usMsg;
 {
     int i;
     HPS hps;


     if (hwnd == global.hwnd)
         if (global.fDragging)
         {
             global.fDragging = FALSE;
             if (global.usPtGrabbed != -1)
             {
                 if (usMsg == WM_BUTTON2UP)
                 {
                     hps = WinGetPS(hwnd);
                     Paint(hps, FALSE);

                     if ((i = global.usPtGrabbed) < (int) global.cptl-1)
                         while (i < (int) global.cptl-1)
                         {
                             global.pptl[i] = global.pptl[i+1];
                             ++i;
                         }

                     --global.cptl;
                     global.usPtGrabbed = -1;

                     Paint(hps, FALSE);
                     WinReleasePS(hps);
                 }
                 else        /* WM_BUTTON1UP */
                     global.usPtGrabbed = -1;
             }
         }
 }




 /************************************************************************
 *
 *   ButtonDown
 *
 ************************************************************************/

 VOID
 ButtonDown(hwnd, usMsg, mp1)
 HWND hwnd;
 USHORT usMsg;
 MPARAM mp1;
 {
     POINTL ptl;
     HPS hps;
     USHORT usNewPtGrabbed;


     if (hwnd == global.hwnd)
         if (!global.fDragging)
         {
             global.fDragging = TRUE;

             ptl.x = (LONG) LOUSHORT(mp1);
             ptl.y = (LONG) HIUSHORT(mp1);

             if ((usNewPtGrabbed = IsPtInList(&ptl)) != -1)
                 global.usPtGrabbed = usNewPtGrabbed;

             if (usMsg == WM_BUTTON1DOWN)
             {
                 hps = WinGetPS(hwnd);
                 Paint(hps, FALSE);

                 if (usNewPtGrabbed == -1)
                     global.usPtGrabbed = AddPtToList(&ptl);
                 else
                     global.usPtGrabbed = usNewPtGrabbed;

                 Paint(hps, FALSE);
                 WinReleasePS(hps);

                 if (global.usPtGrabbed == -1)
                     MyMessageBox(global.hwnd, "Cannot add any more points.");
             }
         }
 }




 /************************************************************************
 *
 *   IsPtInList
 *
 ************************************************************************/

 USHORT
 IsPtInList(pptl)
 PPOINTL pptl;
 {
     int i;


     /* try to find pptl in the points we already have */
     for (i = 0; i < (int) global.cptl; ++i)
         if (((abs(pptl->x - global.pptl[i].x))
                 <= (LONG) global.ulHitPrecision)
          && ((abs(pptl->y - global.pptl[i].y))
                 <= (LONG) global.ulHitPrecision))
                 return i;

     /* couldn't find it */
     return -1;
 }




 /************************************************************************
 *
 *   AddPtToList
 *
 ************************************************************************/

 USHORT
 AddPtToList(pptl)
 PPOINTL pptl;
 {
     int i, j;

     if (global.cptl < CPTLMAX)
     {
         /* check for new points lying on a line segment */
         for (i = 0; i < (int) (global.cptl - 1L); ++i)
             if (IsPtCloseToLine(&global.pptl[i], &global.pptl[i+1], pptl))
             {
                 for (j = (int) global.cptl; j > i+1; --j)
                     global.pptl[j] = global.pptl[j - 1];
                 global.pptl[i+1] = *pptl;
                 ++global.cptl;
                 return i+1;
             }

         /* append the point */

         i = (int) global.cptl;
         global.pptl[i] = *pptl;
         ++global.cptl;
         return i;
     }

     return -1;
 }




 /************************************************************************
 *
 *   IsPtCloseToLine
 *
 ************************************************************************/

 BOOL
 IsPtCloseToLine(pptl1, pptl2, pptlTest)
 PPOINTL pptl1;
 PPOINTL pptl2;
 PPOINTL pptlTest;
 {
     POINTL ptlLL, ptlUR;
     LONG dx, dy, yIntercept, result;


     /* find the bounding box of the line segment */

     ptlLL = *pptl1;        /* assume line goes lower left to upper right */
     ptlUR = *pptl2;
     if (pptl1->x > pptl2->x)
         SwapLong(&ptlLL.x, &ptlUR.x);
     if (pptl1->y > pptl2->y)
         SwapLong(&ptlLL.y, &ptlUR.y);


     /* adjust the bounding box if it's too narrow */

     dx = pptl2->x - pptl1->x;
     if (abs(dx) <= (LONG) (global.ulHitPrecision >> 1))
     {
         ptlLL.x -= (LONG) (global.ulHitPrecision >> 1);
         ptlUR.x += (LONG) (global.ulHitPrecision >> 1);
     }
     dy = pptl2->y - pptl1->y;
     if (abs(dy) <= (LONG) (global.ulHitPrecision >> 1))
     {
         ptlLL.y -= (LONG) (global.ulHitPrecision >> 1);
         ptlUR.y += (LONG) (global.ulHitPrecision >> 1);
     }


     /* see if the test point is in the bounding box of the line segment */

     if ((pptlTest->x >= ptlLL.x) &&
         (pptlTest->x <= ptlUR.x) &&
         (pptlTest->y >= ptlLL.y) &&
         (pptlTest->y <= ptlUR.y))
     {
         /* test for special cases */

         if (dx == 0)
         {
             if (abs(pptlTest->x - pptl1->x) <= (LONG) global.ulHitPrecision)
                 return TRUE;
             else
                 return FALSE;
         }

         if (dy == 0)
         {
             if (abs(pptlTest->y - pptl1->y) <= (LONG) global.ulHitPrecision)
                 return TRUE;
             else
                 return FALSE;
         }


         /* test for general case */

         yIntercept = pptl1->y - (pptl1->x * dy) / dx;

         result = pptlTest->y - (pptlTest->x * dy / dx) - yIntercept;
         if (abs(result) <= (LONG) global.ulHitPrecision)
             return TRUE;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   SwapLong
 *
 ************************************************************************/

 VOID
 SwapLong(pl1, pl2)
 PLONG pl1, pl2;
 {
     LONG lTmp;

     lTmp = *pl1;
     *pl1 = *pl2;
     *pl2 = lTmp;
 }




 /************************************************************************
 *
 *   Close
 *
 ************************************************************************/

 VOID
 Close(hwnd)
 HWND hwnd;
 {
     WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
 }




 /************************************************************************
 *
 *   Command
 *
 *   Dispatches menu commands to the proper handlers.
 *
 ************************************************************************/

 #define UPDATE_MENU_BOOL(var, val)                                \
         {                                                        \
             TOGGLE_BOOL((var));                                 \
             TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var));        \
         }

 #define UPDATE_MENU_LIST(var, val)                                \
         {                                                        \
             UNCHECK_MENU_ITEM(global.hwndFrame, (var));         \
             (var) = (val);                                        \
             CHECK_MENU_ITEM(global.hwndFrame, (var));                \
         }

 VOID
 Command(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     HPS hps;
     BOOL fRedraw = FALSE;
     int rc;

     switch (id)
     {
     case IDM_ABOUT:
         rc = WinDlgBox(HWND_DESKTOP, hwnd, AboutDlgProc, (HMODULE) NULL, IDD_
         fRedraw = FALSE;
         break;

     case IDM_NOPRIM:
         global.flPrim = 0L;
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYLINE, 0);
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYFILLET, 0);
         TOGGLE_MENU_ITEM(global.hwndFrame, IDM_POLYSPLINE, 0);
         fRedraw = TRUE;
         break;

     case IDM_POLYLINE:
         global.flPrim ^= PRIM_POLYLINE;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYLINE
         fRedraw = TRUE;
         break;

     case IDM_POLYFILLET:
         global.flPrim ^= PRIM_POLYFILLET;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYFILL
         fRedraw = TRUE;
         break;

     case IDM_POLYSPLINE:
         global.flPrim ^= PRIM_POLYSPLINE;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POLYSPLI
         fRedraw = TRUE;
         break;

     case IDM_POINTARC:
         global.flPrim ^= PRIM_POINTARC;
         TOGGLE_MENU_ITEM(global.hwndFrame, id, (global.flPrim & PRIM_POINTARC
         fRedraw = TRUE;
         break;

     case IDM_CTLPOINTS:
         UPDATE_MENU_BOOL(global.fDisplayControlPoints, IDM_CTLPOINTS);
         fRedraw = TRUE;
         break;

     case IDM_CLEARALL:
         global.cptl = 0L;
         fRedraw = TRUE;
         break;

     case IDM_COPY:
         /*
             To put this image on the clipboard, create a Metafile DC.

             Associate a presentation space with the DC, then play the
             drawing orders into the metafile.
         */
         global.dop.pszLogAddress = NULL;
         global.dop.pszDriverName = "DISPLAY";
         global.dop.pdriv = NULL;
         global.dop.pszDataType = NULL;

         global.hdcMetafile = DevOpenDC(global.hab, OD_METAFILE,
                             "*", 4L, (PDEVOPENDATA) &global.dop, NULL);
         global.hpsMetafile = GpiCreatePS(global.hab, global.hdcMetafile,
                                     &global.sizlPage, PU_PELS | GPIA_ASSOC);

         Paint(global.hpsMetafile, TRUE);
         /*
             Clean up.  A handle to the metafile is obtained when
             calling DevCloseDC().
         */
         GpiAssociate(global.hpsMetafile, NULL);
         GpiDestroyPS(global.hpsMetafile);
         global.hItem = (ULONG) DevCloseDC(global.hdcMetafile);
         /*
             Be sure to empty the clipboard of other data.  This will
             also empty previous data stored in other formats.
             Then, set the clipboard data with type METAFILE, passing
             the handle to our metafile.
         */
         if (WinOpenClipbrd(global.hab)) {
             WinEmptyClipbrd(global.hab);
             WinSetClipbrdData(global.hab,global.hItem, CF_METAFILE, CFI_HANDL
             WinCloseClipbrd(global.hab);
         }
         break;
     }

     if (fRedraw)
     {
         hps = WinGetPS(hwnd);
         Paint(hps, TRUE);
         WinReleasePS(hps);
     }
 }




 /************************************************************************
 *
 *   Paint
 *
 ************************************************************************/

 VOID
 Paint(hps, fClearScreen)
 HPS  hps;
 BOOL fClearScreen;
 {
     LINEBUNDLE lb;
     RECTL rcl;
     if (fClearScreen)
     {
         /* clear the screen */
         WinQueryWindowRect(global.hwnd, &rcl);
         GpiBitBlt(hps, NULL, 2L, (PPOINTL) &rcl, ROP_ONE, 0L);
     }


     if (global.cptl > 0L)
     {
         if (global.fDisplayControlPoints)
         {
             if (fClearScreen)
                 /* draw all the control points */
                 DrawControlPoints(hps, global.cptl, global.pptl);
             else if (global.usPtGrabbed != -1)
                 /* draw just the control point that moved */
                 DrawControlPoints(hps, 1L, global.pptl+global.usPtGrabbed);
         }

         /* set mix mode to xor */
         lb.usMixMode = FM_XOR;
         GpiSetAttrs(hps, PRIM_LINE, LBB_MIX_MODE, 0L, &lb);

         /* draw the current primitives */

         if (global.flPrim & PRIM_POLYLINE)
         {
             lb.lColor = CLR_BROWN;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYLINE);
         }

         if (global.flPrim & PRIM_POLYFILLET)
         {
             lb.lColor = CLR_DARKCYAN;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYFILLET);
         }

         if (global.flPrim & PRIM_POLYSPLINE)
         {
             lb.lColor = CLR_DARKPINK;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POLYSPLINE);
         }

         if (global.flPrim & PRIM_POINTARC)
         {
             lb.lColor = CLR_BACKGROUND;
             GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
             DrawPrimitive(hps, IDM_POINTARC);
         }
     }
 }




 /************************************************************************
 *
 *   DrawPrimitive
 *
 ************************************************************************/

 VOID
 DrawPrimitive(hps, usPrim)
 HPS hps;
 USHORT usPrim;
 {
     switch ( usPrim )
     {
     case IDM_POLYLINE:
         DrawPolyLine(hps);
         break;

     case IDM_POLYFILLET:
         DrawPolyFillet(hps);
         break;

     case IDM_POLYSPLINE:
         DrawPolySpline(hps);
         break;

     case IDM_POINTARC:
         DrawPointArc(hps);
         break;
     }
 }




 /************************************************************************
 *
 *   DrawPolyLine
 *
 ************************************************************************/

 VOID
 DrawPolyLine(hps)
 HPS hps;
 {
     GpiSetCurrentPosition( hps, global.pptl );
     GpiPolyLine( hps, global.cptl-1L, global.pptl+1 );
 }




 /************************************************************************
 *
 *   DrawPolyFillet
 *
 ************************************************************************/

 VOID
 DrawPolyFillet(hps)
 HPS hps;
 {
     if (global.cptl > 2)
     {
         GpiSetCurrentPosition( hps, global.pptl );
         GpiPolyFillet( hps, global.cptl-1L, global.pptl+1 );
     }
 }




 /************************************************************************
 *
 *   DrawPolySpline
 *
 ************************************************************************/

 VOID
 DrawPolySpline(hps)
 HPS hps;
 {
     USHORT cptSlack;        /* # points in pptl not usable by PolySpline */

     /* GpiPolySpline expects the number of points to be a
        multiple of 3.  If we have a non-multiple of three,
        (excluding the first point, which we've used to set
        the current position), only pass the largest multiple
        of three, saving the rest for the next go-round. */

     cptSlack = (int)((global.cptl-1L) % 3) + 1;
     GpiSetCurrentPosition( hps, global.pptl );
     GpiPolySpline( hps, global.cptl-cptSlack,
                    global.pptl+1 );
 }




 /************************************************************************
 *
 *   DrawPointArc
 *
 ************************************************************************/

 VOID
 DrawPointArc(hps)
 HPS hps;
 {
     if (global.cptl >= 3L)
     {
         GpiSetCurrentPosition( hps, global.pptl );
         GpiPointArc( hps, global.pptl+1 );
     }
 }




 /************************************************************************
 *
 *   DrawControlPoints
 *
 ************************************************************************/

 VOID
 DrawControlPoints(hps, cptl, pptl)
 HPS hps;
 LONG cptl;
 PPOINTL pptl;
 {
     MARKERBUNDLE mb;

     mb.lColor = CLR_TRUE;
     mb.usMixMode = FM_XOR;
     GpiSetAttrs(hps, PRIM_MARKER, MBB_COLOR | MBB_MIX_MODE, 0L, &mb);

     GpiPolyMarker(hps, cptl, pptl);
 }




 /************************************************************************
 *
 *   MyMessageBox
 *
 *   Displays a message box with the given string.  To simplify matters,
 *   the box will always have the same title ("PolyLine Editor"), will always
 *   have a single button ("Ok"), will always have an exclamation point
 *   icon, and will always be application modal.
 *
 ************************************************************************/

 VOID
 MyMessageBox(hWnd, sz)
 HWND hWnd;
 PSZ sz;
 {
     static char *szTitle = "PolyLine Editor";

     WinMessageBox(HWND_DESKTOP, hWnd, sz, szTitle, 0,
                   MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
 }

 MRESULT CALLBACK AboutDlgProc(HWND hDlg, USHORT msg, MPARAM mp1, MPARAM mp2)
 /*
     About... dialog procedure
 */
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 EXPAND.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\EXPAND\EXPAND.C

 #define INCL_PM
 #include <OS2.H>
 #include "Expand.H"

 MRESULT EXPENTRY WndProc      (HWND, USHORT, MPARAM, MPARAM );
 MRESULT EXPENTRY AboutDlgProc (HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY DialogProc  (HWND, USHORT, MPARAM, MPARAM);

 int cdecl main(void);

 char szAppName[] = "Expand";

 HAB        hAB;
 HMQ        hmqMsgQueue;
 HWND        hWndMain,
         hWndFrame;

 int cdecl main()
 {
     QMSG    qmsg;
     ULONG   ctlData;

     hAB = WinInitialize (0);
     hmqMsgQueue = WinCreateMsgQueue (hAB, 0);

     if (!WinRegisterClass (hAB,
                            szAppName,
                            WndProc,
                            CS_SYNCPAINT | CS_SIZEREDRAW,
                            0)) {
         return(0);
     }


     ctlData = FCF_STANDARD;
     hWndFrame = WinCreateStdWindow ( HWND_DESKTOP,
                                     WS_VISIBLE,
                                     &ctlData,
                                     szAppName,
                                     NULL,
                                     0L,
                                     0,
                                     ID_RESOURCE,
                                     &hWndMain);
     WinSetWindowText (hWndFrame, szAppName);
     WinShowWindow (hWndFrame, TRUE);

     while ( WinGetMsg (hAB, &qmsg, NULL, 0, 0)) {
         WinDispatchMsg (hAB, &qmsg);
     }

     WinDestroyWindow   (hWndFrame);
     WinDestroyMsgQueue (hmqMsgQueue);
     WinTerminate       (hAB);
 }

 MRESULT EXPENTRY WndProc (hWnd, msg, mp1, mp2)
     HWND    hWnd;
     USHORT  msg;
     MPARAM  mp1, mp2;
 {
     HPS    hPS;
     RECTL   rclPaint, rclWindow;
     POINTL  ptlPatternRef;

     switch (msg) {

         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {
                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hWnd, AboutDlgProc,
                         (HMODULE) NULL, ID_RESOURCE, NULL);
                     break;

                 case IDM_ITEM+1:
                     WinDlgBox(HWND_DESKTOP, hWnd, DialogProc,
                         (HMODULE) NULL, ID_DIALOG, NULL);
                     break;

                 case IDM_ITEM+2:
                 case IDM_ITEM+3:
                 case IDM_ITEM+4:
                     break;

                 case IDM_EXIT:
                     WinPostMsg (hWnd, WM_CLOSE, 0L, 0L);
                     break;
             }
             break;

         case WM_HELP:
             WinMessageBox (HWND_DESKTOP, hWnd,
                 "Help Not Implemented Yet.",
                 " - Help - ",
                 0,
                 MB_OK | MB_MOVEABLE | MB_APPLMODAL);
             break;

         case WM_CLOSE:
             WinPostMsg (hWnd, WM_QUIT, 0L, 0L);
             break;

         case WM_PAINT:
             hPS = WinBeginPaint (hWnd, NULL, &rclPaint);

             WinQueryWindowRect(hWnd, &rclWindow);
             ptlPatternRef.x = rclWindow.xLeft;
             ptlPatternRef.y = rclWindow.yTop;
             GpiSetPatternRefPoint(hPS, &ptlPatternRef);
             WinFillRect(hPS, &rclPaint, SYSCLR_APPWORKSPACE);

             WinEndPaint (hPS);
             break;

         default:
             return (WinDefWindowProc (hWnd, msg, mp1, mp2));
     }
     return 0L;
 }


 MRESULT EXPENTRY AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
     switch (msg) {
         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {
                 case IDB_OK:
                     WinDismissDlg (hwnd, TRUE);
                     return 0;
             }
             break;
      }
      return WinDefDlgProc (hwnd, msg, mp1, mp2);
 }

 MRESULT EXPENTRY DialogProc (HWND hDlg, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
     SWP  swpDlg, swpParent, swpOwner;
     static HWND option, button1, button2, button3, button4;

     switch (msg) {

         case WM_INITDLG:
             WinSendDlgItemMsg (hDlg, 256, EM_SETTEXTLIMIT, MPFROMSHORT(8), 0L
             WinQueryWindowPos (hDlg, &swpDlg);
             WinQueryWindowPos (WinQueryWindow(hDlg, QW_PARENT, FALSE), &swpPa
             WinQueryWindowPos (WinQueryWindow(hDlg, QW_OWNER, FALSE), &swpOwn
             swpDlg.x = swpOwner.x + ((swpOwner.cx / 2) - ((swpDlg.cx+120) / 2
             swpDlg.y = swpOwner.y + ((swpOwner.cy / 2) - (swpDlg.cy / 2));
             WinSetMultWindowPos (hAB, &swpDlg, 1);
             option  = WinWindowFromID (hDlg, IDB_OPTION);
             button1 = WinWindowFromID (hDlg, IDB_RADIO1);
             WinShowWindow (button1, FALSE);
             button2 = WinWindowFromID (hDlg, IDB_RADIO2);
             WinShowWindow (button2, FALSE);
             button3 = WinWindowFromID (hDlg, IDB_RADIO3);
             WinShowWindow (button3, FALSE);
             button4 = WinWindowFromID (hDlg, IDB_RADIO4);
             WinShowWindow (button4, FALSE);
             break;

         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {
                 case IDB_OPTION:
                     WinQueryWindowPos (hDlg, &swpDlg);
                     swpDlg.fs = SWP_SIZE;
                     swpDlg.cx += 120;
                     WinSetMultWindowPos (hAB, &swpDlg, 1);
                     WinEnableWindow (option, FALSE);
                     WinShowWindow (button1, TRUE);
                     WinShowWindow (button2, TRUE);
                     WinShowWindow (button3, TRUE);
                     WinShowWindow (button4, TRUE);
                     WinSetFocus (HWND_DESKTOP, button1);
                     return FALSE;

                 case IDB_OK:
                 case IDB_CANCEL:
                     WinDismissDlg (hDlg, TRUE);
                     return 0;
             }
             break;
      }
      return WinDefDlgProc (hDlg, msg, mp1, mp2);
 }


 FATPEL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\FATPEL\FATPEL.C

 /************************************************************************
 *
 *   fatpel.c -- The Diamond Metric, Theory vs. Practice
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINMESSAGEMGR
 #define        INCL_WINPOINTERS
 #define INCL_WINSWITCHLIST
 #define INCL_WINTRACKRECT
 #define INCL_WINDIALOGS
 #define INCL_WINBUTTONS
 #define INCL_GPILOGCOLORTABLE
 #define INCL_GPIBITMAPS
 #define        INCL_GPITRANSFORMS
 #define INCL_DOSMEMMGR
 #define INCL_DOSFILEMGR
 #define INCL_BITMAPFILEFORMAT
 #define INCL_GPIPRIMITIVES
 #define INCL_WINMENUS
 #define INCL_GPIREGIONS
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #include "opendlg.h"
 #include "fatpel.h"




 /************************************************************************
 *
 *   Function declarations
 *
 ************************************************************************/

 /* Private functions */

 VOID  cdecl main(VOID);
 BOOL  FAR InitGlobals(VOID);
 BOOL  FAR InitApp(VOID);
 VOID  Close(HWND);
 VOID  Command(HWND, USHORT);
 VOID  Paint(HPS, USHORT);
 VOID  EraseBackground(HPS);
 VOID  DrawGrid(HPS);
 VOID  DisplayRenderedPels(HPS, USHORT);
 VOID  DisplayControlPoints(HPS, LONG, PPOINTL, USHORT);
 VOID  DisplayMathematicalObject(HPS, USHORT);
 VOID  DrawFatPels(HPS);
 VOID  DrawOneFatPel(HPS, PPOINTL, COLOR);
 VOID  GetFatPelFromPt(PPOINTL, PPOINTL);
 VOID  SetFatPel(PPOINTL, COLOR);
 VOID  RoundControlPoints(HPS, LONG, PPOINTL, PPOINTL, LONG, LONG);
 VOID  ComputeTransform(PRECTL, PRECTL);
 VOID  DrawPrimitive(HPS, LONG, PPOINTL);
 VOID  UpdateSurfaceDims(VOID);
 VOID  MySetWindowLong        (HWND, USHORT, LONG);
 VOID  MySetWindowLongHex(HWND, USHORT, LONG);
 LONG  MyGetWindowLong        (HWND, USHORT);
 VOID  MouseMove(HWND, MPARAM);
 VOID  ButtonUp(HWND, USHORT);
 VOID  ButtonDown(HWND, USHORT, MPARAM);
 VOID  DragPelSize(HWND, POINTS);
 VOID  WriteFile(HWND, HPS);
 BOOL  WriteBMP(HFILE, HPS, PBITMAPINFOHEADER);
 VOID  MyMessageBox(HWND, PSZ);
 VOID  SaveWindowToFile(HWND);
 SHORT IsPtInList(PPOINTL);
 SHORT AddPtToList(PPOINTL);
 BOOL  IsPtCloseToLine(PPOINTL, PPOINTL, PPOINTL);
 VOID  SwapLong(PLONG, PLONG);


 /* Exported functions */

 ULONG CALLBACK WndProc         (HWND, USHORT, MPARAM, MPARAM);
 ULONG CALLBACK AboutDlg  (HWND, USHORT, MPARAM, MPARAM);
 ULONG CALLBACK ColorsDlg (HWND, USHORT, MPARAM, MPARAM);
 ULONG CALLBACK PelSizeDlg(HWND, USHORT, MPARAM, MPARAM);




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 /* compute absolute value for arbitrary (in my case, LONG) number */
 /* this is to avoid compiler warnings about data conversion */
 #define L_ABS(x)        (((x) > 0) ? (x) : (-(x)))

 typedef struct
 {
     HAB      hab;
     HMQ      hMsgQ;
     HWND     hwndFrame;
     HWND     hwnd;

     BOOL     fFirstTime;   /* TRUE --> first time initialization of rcl */
     RECTL    rcl;           /* dimensions of client rectangle */

     HPS      hpsFat;
     HDC      hdcFat;
     HBITMAP  hbmFat;
     HPS      hpsFatShadow;
     HDC      hdcFatShadow;
     HBITMAP  hbmFatShadow;

     RECTL    rclFatBM;           /* dimensions of fatbits bitmap */
     RECTL    rclFat;           /* dimensions of active fat bits grid */
     LONG     cxFatPel;            /* width of fat pel */
     LONG     cyFatPel;            /* height of fat pel */
     LONG     cxHalfFatPel;
     LONG     cyHalfFatPel;
     USHORT   usPelShape;

     MATRIXLF matlf;        /* goes from window coords to fatpel coords */

     BOOL     fRGB;           /* TRUE --> color mode is RGB */
     COLOR    clrMathObj;
     COLOR    clrRenderedObj;
     COLOR    clrField;
     COLOR    clrCrossHair;
     COLOR    clrInterstice;
     COLOR    clrControlPoints;

     COLOR    clrBlackIndex;
     COLOR    clrEditPel;

     USHORT   usControlPointSymbol;

     BOOL     fDisplayRenderedObj;
     BOOL     fDisplayMathObj;
     BOOL     fDisplayControlPoints;
     BOOL     fDisplayCrossHairs;
     BOOL     fDisplayPelBorder;
     BOOL     fRoundControlPoints;
     BOOL     fAutoRedraw;
     USHORT   usCurPrim;
     USHORT   usMix;

     LONG     cptl;
     PPOINTL  pptl;
     PPOINTL  pptlTmp;

     BOOL     fDraggingPelSize;
     HPOINTER hptrDragSize;

     BOOL     fDraggingPelColor;
     HPOINTER hptrDragColor;

     SHORT    sPtGrabbed;
     BOOL     fDraggingControlPoint;
     LONG     lHitPrecision;

     BOOL     fEditPelColors;

 } GLOBALDATA;
 GLOBALDATA global;




 /************************************************************************
 *
 *   main
 *
 *   WinInitialize resizes our ring 2 stack, among other things, so
 *   we won't GP fault trying to do graphics.  WinCreateMsgQueue defines
 *   us as a REAL PM app. (WINDOWAPI in .DEF file does also).
 *   Call a sub to register our window class and create a window.
 *   Loop over messages.  Exit cleanly.
 *
 ************************************************************************/

 VOID cdecl
 main()
 {
     QMSG qMsg;
     int iRet = 0;


     global.hab         = WinInitialize(0);
     global.hMsgQ = WinCreateMsgQueue(global.hab, 0);

     if (InitApp())
         while (WinGetMsg( global.hab, (PQMSG)&qMsg, (HWND)NULL, 0, 0 ))
             WinDispatchMsg( global.hab, (PQMSG)&qMsg );
     else
         iRet = -1;

     WinDestroyWindow( global.hwndFrame );
     WinDestroyMsgQueue( global.hMsgQ );
     WinTerminate( global.hab );
     DosExit(EXIT_PROCESS, iRet);
 }




 /****************************************************************************
 *
 *   InitGlobals
 *
 *   Initialize global variables.
 *
 ****************************************************************************/

 BOOL FAR
 InitGlobals()
 {
     global.fFirstTime                 = TRUE;

     global.rcl.xLeft                 = 0L;
     global.rcl.yBottom                 = 0L;
     global.rcl.xRight                 = 0L;
     global.rcl.yTop                 = 0L;

     global.hpsFat                 = NULL;
     global.hdcFat                 = NULL;
     global.hbmFat                 = NULL;
     global.hpsFatShadow          = NULL;
     global.hdcFatShadow          = NULL;
     global.hbmFatShadow          = NULL;
     global.rclFatBM.xLeft         = 0L;
     global.rclFatBM.yBottom         = 0L;
     global.rclFatBM.xRight         = 0L;
     global.rclFatBM.yTop         = 0L;

     global.cxFatPel                 = 32L;
     global.cyFatPel                 = 32L;
     global.cxHalfFatPel          = global.cxFatPel / 2L;
     global.cyHalfFatPel          = global.cyFatPel / 2L;
     global.usPelShape                 = IDD_CIRCLE;

     global.fRGB                  = FALSE;
     global.clrMathObj                 = CLR_BLUE;
     global.clrRenderedObj         = CLR_NEUTRAL;
     global.clrField                 = CLR_CYAN;
     global.clrCrossHair          = CLR_DARKCYAN;
     global.clrInterstice         = CLR_BACKGROUND;
     global.clrControlPoints         = CLR_YELLOW;

     global.clrBlackIndex         = CLR_ERROR;
     global.clrEditPel                 = CLR_ERROR;

     global.usControlPointSymbol  = MARKSYM_SOLIDDIAMOND;

     global.fDisplayRenderedObj         = TRUE;
     global.fDisplayMathObj         = TRUE;
     global.fDisplayControlPoints = TRUE;
     global.fDisplayCrossHairs         = TRUE;
     global.fDisplayPelBorder         = TRUE;
     global.fRoundControlPoints         = FALSE;
     global.fAutoRedraw                 = TRUE;
     global.usCurPrim                 = IDM_POLYLINE;
     global.usMix                 = FM_OVERPAINT;

     global.fDraggingPelSize         = FALSE;
     global.fDraggingPelColor         = FALSE;
     global.fDraggingControlPoint = FALSE;
     global.sPtGrabbed                 = NO_POINT;
     global.lHitPrecision         = 0L;

     global.fEditPelColors         = FALSE;


     global.cptl = 0L;
     global.pptl = NULL;
     if (DosAllocSeg(CPTLMAX * sizeof(POINTL),
                    ((PUSHORT)&global.pptl)+1, 0))
         return FALSE;
     global.pptlTmp = NULL;
     if (DosAllocSeg(CPTLMAX * sizeof(POINTL),
                    ((PUSHORT)&global.pptlTmp)+1, 0))
         return FALSE;

     return TRUE;
 }




 /****************************************************************************
 *
 *   InitApp
 *
 *   Register application window class and creates standard window.
 *
 ****************************************************************************/

 #define INIT_MENU_ITEM(val, var)     \
         TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var))

 BOOL FAR
 InitApp()
 {
     char szTitle[24];
     ULONG ctldata;
     PID pid;
     TID tid;
     HSWITCH hsw;
     static SWCNTRL swctl = { 0, 0, 0, 0, 0, SWL_VISIBLE,
                              SWL_JUMPABLE, "FatPels", 0 };

     if (!InitGlobals())
         return FALSE;


     /*  Register Application Window Class  */

     WinLoadString( global.hab, (HMODULE) NULL, IDS_TITLE, sizeof(szTitle), (P
     if ( !WinRegisterClass( global.hab, (PCH)szTitle, (PFNWP)WndProc,
             CS_SIZEREDRAW, 0 ))
         return FALSE;


     /* Load the pointer to use when dragging pel size. */
     if (!(global.hptrDragSize = WinLoadPointer( HWND_DESKTOP, (HMODULE) NULL,
         return FALSE;

     /* Load the pointer to use when dragging pel color. */
     if (!(global.hptrDragColor = WinLoadPointer( HWND_DESKTOP, (HMODULE) NULL
         return FALSE;


     /* Create a window instance of class "FatPel" */

     ctldata = FCF_STANDARD &
      ~(ULONG)(FCF_ICON | FCF_ACCELTABLE | FCF_TASKLIST);

     if (global.hwndFrame = WinCreateStdWindow(
         HWND_DESKTOP,                   /* specify desktop as parent window
         WS_VISIBLE,                   /* window styles
         &ctldata,                   /* frame creation flags
         (PCH)szTitle,                   /* window class name
         (PCH)szTitle,                   /* name appearing in window caption
         0L,                           /*
         (HMODULE)NULL,                   /* use current executable module id
         IDR_FATPEL,                   /* menu id
         (HWND FAR *)&global.hwnd   /* window handle
         ))
     {
         INIT_MENU_ITEM(IDM_RENDEREDOBJ,         global.fDisplayRenderedObj);
         INIT_MENU_ITEM(IDM_MATHOBJ,         global.fDisplayMathObj);
         INIT_MENU_ITEM(IDM_CTLPOINTS,         global.fDisplayControlPoints);
         INIT_MENU_ITEM(IDM_CROSSHAIRS,         global.fDisplayCrossHairs);
         INIT_MENU_ITEM(IDM_PELBORDER,         global.fDisplayPelBorder);
         INIT_MENU_ITEM(IDM_ROUNDPOINTS,         global.fRoundControlPoints);
         INIT_MENU_ITEM(IDM_AUTOREDRAW,         global.fAutoRedraw);
         INIT_MENU_ITEM(IDM_EDITPELCOLORS, global.fEditPelColors);

         CHECK_MENU_ITEM(global.hwndFrame, global.usCurPrim);


         /* Add ourselves to the switch list. */

         WinQueryWindowProcess(global.hwndFrame, &pid, &tid);
         swctl.hwnd        = global.hwndFrame;
         swctl.idProcess = pid;
         hsw = WinAddSwitchEntry(&swctl);

         return TRUE;
     }
     return FALSE;
 }




 /*************************************************************************
 *
 *   WndProc
 *
 *   Process messages for the window class.
 *
 ************************************************************************/

 ULONG CALLBACK
 WndProc( hwnd, usMsg, mp1, mp2 )
 HWND        hwnd;
 USHORT        usMsg;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     switch (usMsg)
     {
     case WM_CLOSE:
         Close(hwnd);
         break;

     case WM_COMMAND:
         Command(hwnd, LOUSHORT(mp1));
         break;

     case WM_PAINT:
         {
             HPS   hps;

             if (global.fFirstTime)
             {
                 SIZEF sizfx;

                 hps = WinGetPS(hwnd);
                 GpiQueryMarkerBox(hps, &sizfx);
                 global.lHitPrecision = sizfx.cx / 0x20000L + 1L;
                 WinReleasePS(hps);

                 UpdateSurfaceDims();
                 global.fFirstTime = FALSE;
             }

             /* The small bitmap may have been resized since we last
              * painted, in which case it will have been initialized to
              * the field color.  Therefore, we will render the mathematical
              * object to make sure the right fatpels are there.
              */
             global.usMix = FM_OVERPAINT;
             hps = WinBeginPaint(global.hwnd, NULL, NULL);
             Paint(hps, CLEAR_BACKGROUND|RENDER_MATH_OBJ);
             WinEndPaint(hps);
         }
         break;

     case WM_BUTTON1DOWN:
     case WM_BUTTON2DOWN:
         ButtonDown(hwnd, usMsg, mp1);
         break;

     case WM_BUTTON1UP:
     case WM_BUTTON2UP:
         ButtonUp(hwnd, usMsg);
         break;

     case WM_MOUSEMOVE:
         MouseMove(hwnd, mp1);
         break;

     case WM_SIZE:
         UpdateSurfaceDims();
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;

     default:
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   MouseMove
 *
 ************************************************************************/

 VOID
 MouseMove(hwnd, mp1)
 HWND hwnd;
 MPARAM mp1;
 {
     POINTL ptl;
     HPS hps;


     /* make sure we still have our pointer */
     /* notice the hierarchy of pointer modes */

     if (global.fDraggingPelSize)
     {
         if (global.hptrDragSize)
             WinSetPointer(HWND_DESKTOP,global.hptrDragSize);
     }
     else if (global.fEditPelColors)
     {
         if (global.hptrDragColor)
             WinSetPointer(HWND_DESKTOP,global.hptrDragColor);
     }
     else
         WinSetPointer(HWND_DESKTOP,
                   WinQuerySysPointer(HWND_DESKTOP,SPTR_ARROW,FALSE));


     if (global.fDraggingPelColor)
     {
         POINTL ptl, ptlFat;
         HPS hps;


         ptl.x = (LONG) LOUSHORT(mp1);
         ptl.y = (LONG) HIUSHORT(mp1);

         /* letting the point go negative causes overflow errors */
         if (ptl.x < 0)
             ptl.x = 0;
         if (ptl.y < 0)
             ptl.y = 0;

         GetFatPelFromPt(&ptl, &ptlFat);
         SetFatPel(&ptlFat, global.clrEditPel);

         hps = WinGetPS(hwnd);
         Paint(hps, OVERRIDE_RENDERED_OBJ);
         Paint(hps, IGNORED);        /* this call just copies fatpels to the s
         WinReleasePS(hps);
     }
     else if (global.fDraggingControlPoint)
     {
         ptl.x = (LONG) LOUSHORT(mp1);
         ptl.y = (LONG) HIUSHORT(mp1);

         /* letting the point go negative causes overflow errors */
         if (ptl.x < 0)
             ptl.x = 0;
         if (ptl.y < 0)
             ptl.y = 0;

         if (global.sPtGrabbed != NO_POINT)
         {
             hps = WinGetPS(hwnd);
             Paint(hps, OVERRIDE_RENDERED_OBJ);

             global.pptl[global.sPtGrabbed] = ptl;

             Paint(hps, CLEAR_FAT_BITMAP|RENDER_MATH_OBJ);
             WinReleasePS(hps);
         }
     }
 }




 /************************************************************************
 *
 *   ButtonUp
 *
 ************************************************************************/

 VOID
 ButtonUp(hwnd, usMsg)
 HWND hwnd;
 USHORT usMsg;
 {
     SHORT i;
     HPS hps;


     if (global.fDraggingPelColor)
     {
         global.fDraggingPelColor = FALSE;
         WinSetCapture(HWND_DESKTOP, NULL);
     }
     else if (global.fDraggingControlPoint)
     {
         global.fDraggingControlPoint = FALSE;
         WinSetCapture(HWND_DESKTOP, NULL);
         if (global.sPtGrabbed != NO_POINT)
         {
             if (usMsg == WM_BUTTON2UP)        /* remove point? */
             {
                 hps = WinGetPS(hwnd);
                 Paint(hps, OVERRIDE_RENDERED_OBJ);

                 /* squeeze out selected point */
                 if ((i = global.sPtGrabbed) < (SHORT)(global.cptl-1))
                     while (i < (SHORT)(global.cptl-1))
                     {
                         global.pptl[i] = global.pptl[i+1];
                         ++i;
                     }

                 --global.cptl;
                 global.sPtGrabbed = NO_POINT;

                 Paint(hps, CLEAR_FAT_BITMAP|RENDER_MATH_OBJ);
                 WinReleasePS(hps);
             }
             else    /* WM_BUTTON1UP */
                 global.sPtGrabbed = NO_POINT;
         }
     }
 }




 /************************************************************************
 *
 *   ButtonDown
 *
 ************************************************************************/

 VOID
 ButtonDown(hwnd, usMsg, mp1)
 HWND hwnd;
 USHORT usMsg;
 MPARAM mp1;
 {
     if (global.fDraggingPelSize)
     {
         POINTS pt;
         HPS hps;

         pt.x = LOUSHORT(mp1);
         pt.y = HIUSHORT(mp1);
         DragPelSize(hwnd, pt);
         global.fDraggingPelSize = FALSE;

         WinSetPointer(HWND_DESKTOP,
                       WinQuerySysPointer(HWND_DESKTOP,SPTR_ARROW,FALSE));

         hps = WinGetPS(hwnd);
         global.usMix = FM_OVERPAINT;
         Paint(hps, CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ);
         WinReleasePS(hps);
     }
     else if (global.fEditPelColors)
     {
         POINTL ptl, ptlFat;
         HPS hps;

         global.fDraggingPelColor = TRUE;
         WinSetCapture(HWND_DESKTOP, hwnd);

         ptl.x = (LONG) LOUSHORT(mp1);
         ptl.y = (LONG) HIUSHORT(mp1);

         if (global.usMix != FM_XOR)
         {
             hps = WinGetPS(hwnd);
             global.usMix = FM_XOR;
             Paint(hps, CLEAR_BACKGROUND);
             WinReleasePS(hps);
         }

         if (usMsg == WM_BUTTON1DOWN)
             global.clrEditPel = global.clrRenderedObj;
         else
             global.clrEditPel = global.clrField;

         GetFatPelFromPt(&ptl, &ptlFat);
         SetFatPel(&ptlFat, global.clrEditPel);

         hps = WinGetPS(hwnd);
         Paint(hps, OVERRIDE_RENDERED_OBJ);
         Paint(hps, IGNORED);        /* this call just copies fatpels to the s
         WinReleasePS(hps);
     }
     else if (!global.fDraggingControlPoint)
     {
         POINTL ptl;
         SHORT sNewPtGrabbed;
         HPS hps;

         global.fDraggingControlPoint = TRUE;
         WinSetCapture(HWND_DESKTOP, hwnd);

         ptl.x = (LONG) LOUSHORT(mp1);
         ptl.y = (LONG) HIUSHORT(mp1);

         sNewPtGrabbed = IsPtInList(&ptl);

         if (global.usMix != FM_XOR)
         {
             hps = WinGetPS(hwnd);
             global.usMix = FM_XOR;
             Paint(hps, CLEAR_BACKGROUND);
             WinReleasePS(hps);
         }

         if (usMsg == WM_BUTTON1DOWN)        /* add/move point? */
         {
             hps = WinGetPS(hwnd);

             if (sNewPtGrabbed != NO_POINT)
                 global.sPtGrabbed = sNewPtGrabbed;
             Paint(hps, OVERRIDE_RENDERED_OBJ);

             if (sNewPtGrabbed == NO_POINT)
                 global.sPtGrabbed = AddPtToList(&ptl);
             else
                 global.sPtGrabbed = sNewPtGrabbed;

             Paint(hps, CLEAR_FAT_BITMAP|RENDER_MATH_OBJ);
             WinReleasePS(hps);

             if (global.sPtGrabbed == NO_POINT)
                 MyMessageBox(global.hwnd, "Cannot add any more points.");
         }
         else if (sNewPtGrabbed != NO_POINT)
             global.sPtGrabbed = sNewPtGrabbed;
     }
 }




 /************************************************************************
 *
 *   GetFatPelFromPt
 *
 ************************************************************************/

 VOID
 GetFatPelFromPt(pptl, pptlFat)
 PPOINTL pptl;
 PPOINTL pptlFat;
 {
     pptlFat->x = pptl->x / global.cxFatPel;
     pptlFat->y = pptl->y / global.cyFatPel;
 }




 /************************************************************************
 *
 *   SetFatPel
 *
 ************************************************************************/

 VOID
 SetFatPel(pptl, clr)
 PPOINTL pptl;
 COLOR clr;
 {
     LINEBUNDLE lb;

     if (global.hpsFat)
     {
         lb.lColor = clr;
         GpiSetAttrs(global.hpsFat, PRIM_LINE, LBB_COLOR, 0L, &lb);
         GpiSetPel(global.hpsFat, pptl);
     }
 }




 /************************************************************************
 *
 *   IsPtInList
 *
 ************************************************************************/

 SHORT
 IsPtInList(pptl)
 PPOINTL pptl;
 {
     SHORT i;


     /* try to find pptl in the points we already have */
     for (i = 0; i < (SHORT)global.cptl; ++i)
         if (((L_ABS(pptl->x - global.pptl[i].x)) <= global.lHitPrecision) &&
             ((L_ABS(pptl->y - global.pptl[i].y)) <= global.lHitPrecision))
                 return i;

     /* couldn't find it */
     return NO_POINT;
 }




 /************************************************************************
 *
 *   AddPtToList
 *
 ************************************************************************/

 SHORT
 AddPtToList(pptl)
 PPOINTL pptl;
 {
     SHORT i, j;

     if (global.cptl < CPTLMAX)
     {
         /* check for new points lying on a line segment */
         for (i = 0; i < (SHORT)(global.cptl-1L); ++i)
             if (IsPtCloseToLine(&global.pptl[i], &global.pptl[i+1], pptl))
             {
                 /* insert point between endpoints of nearest line segment */
                 for (j = (SHORT)global.cptl; j > i+1; --j)
                     global.pptl[j] = global.pptl[j - 1];
                 global.pptl[i+1] = *pptl;
                 ++global.cptl;
                 return i+1;
             }

         /* append the point */

         i = (SHORT) global.cptl;
         global.pptl[i] = *pptl;
         ++global.cptl;
         return i;
     }

     return NO_POINT;
 }




 /************************************************************************
 *
 *   IsPtCloseToLine
 *
 ************************************************************************/

 BOOL
 IsPtCloseToLine(pptl1, pptl2, pptlTest)
 PPOINTL pptl1;
 PPOINTL pptl2;
 PPOINTL pptlTest;
 {
     POINTL ptlLL, ptlUR;
     LONG dx, dy, yIntercept, error;
     LONG lBoxAdjustment;


     /* find the bounding box of the line segment */

     ptlLL = *pptl1;        /* assume line goes lower left to upper right */
     ptlUR = *pptl2;
     if (pptl1->x > pptl2->x)
         SwapLong(&ptlLL.x, &ptlUR.x);
     if (pptl1->y > pptl2->y)
         SwapLong(&ptlLL.y, &ptlUR.y);


     /* adjust the bounding box if it's too narrow */

     lBoxAdjustment = global.lHitPrecision/2L;

     dx = pptl2->x - pptl1->x;
     if (L_ABS(dx) <= global.lHitPrecision)
     {
         ptlLL.x -= lBoxAdjustment;
         ptlUR.x += lBoxAdjustment;
     }
     dy = pptl2->y - pptl1->y;
     if (L_ABS(dy) <= global.lHitPrecision)
     {
         ptlLL.y -= lBoxAdjustment;
         ptlUR.y += lBoxAdjustment;
     }


     /* see if the test point is in the bounding box of the line segment */

     if ((pptlTest->x >= ptlLL.x) &&
         (pptlTest->x <= ptlUR.x) &&
         (pptlTest->y >= ptlLL.y) &&
         (pptlTest->y <= ptlUR.y))
     {
         /* test for special cases */

         if (dx == 0)        /* vertical line */
         {
             return (L_ABS(pptlTest->x - pptl1->x) <= global.lHitPrecision);
         }

         if (dy == 0)        /* horizontal line */
         {
             return (L_ABS(pptlTest->y - pptl1->y) <= global.lHitPrecision);
         }


         /* test for general case */

         yIntercept = pptl1->y - (pptl1->x * dy) / dx;

         error = pptlTest->y - (pptlTest->x * dy / dx) - yIntercept;
         if (L_ABS(error) <= global.lHitPrecision)
             return TRUE;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   SwapLong
 *
 ************************************************************************/

 VOID
 SwapLong(pl1, pl2)
 PLONG pl1, pl2;
 {
     LONG lTmp;

     lTmp = *pl1;
     *pl1 = *pl2;
     *pl2 = lTmp;
 }




 /************************************************************************
 *
 *   DragPelSize
 *
 *   Set the dimensions of a fat pel by dragging a rectangle
 *   on the screen.
 *
 ************************************************************************/

 VOID
 DragPelSize(hwnd, pt)
 HWND hwnd;
 POINTS pt;
 {
     TRACKINFO ti;

     WinSendMsg(global.hwndFrame, WM_QUERYTRACKINFO, (MPARAM)TF_MOVE, (MPARAM)

     ti.cxBorder   = 1;
     ti.cyBorder   = 1;
     ti.rclTrack.xLeft        = (LONG)pt.x;
     ti.rclTrack.yBottom = (LONG)pt.y;
     ti.rclTrack.xRight        = (LONG)pt.x;
     ti.rclTrack.yTop        = (LONG)pt.y;
     ti.fs = TF_RIGHT | TF_TOP;
     ti.ptlMinTrackSize.x = 1L;
     ti.ptlMinTrackSize.y = 1L;

     if (WinTrackRect(hwnd, NULL, &ti))
     {
         global.cxFatPel = (ti.rclTrack.xRight - ti.rclTrack.xLeft)  ;
         global.cyFatPel = (ti.rclTrack.yTop   - ti.rclTrack.yBottom);

         if (global.cxFatPel < 1L)
             global.cxFatPel = 1L;

         if (global.cyFatPel < 1L)
             global.cyFatPel = 1L;

         global.cxHalfFatPel = global.cxFatPel / 2L;
         global.cyHalfFatPel = global.cyFatPel / 2L;

         UpdateSurfaceDims();
     }
 }




 /************************************************************************
 *
 *   Close
 *
 ************************************************************************/

 VOID
 Close(hwnd)
 HWND hwnd;
 {
     if (global.hptrDragSize)
         WinDestroyPointer(global.hptrDragSize);
     WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
 }




 /************************************************************************
 *
 *   Command
 *
 *   Dispatches menu commands to the proper handlers.
 *
 ************************************************************************/

 #define UPDATE_MENU_BOOL(var, val)                                \
         {                                                        \
             TOGGLE_BOOL((var));                                 \
             TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var));        \
         }

 #define UPDATE_MENU_LIST(var, val)                                \
         {                                                        \
             UNCHECK_MENU_ITEM(global.hwndFrame, (var));         \
             (var) = (val);                                        \
             CHECK_MENU_ITEM(global.hwndFrame, (var));                \
         }

 VOID
 Command(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     BOOL fRedraw = FALSE;
     USHORT fsCmd = IGNORED;


     switch (id)
     {
     case IDM_SAVE:
         SaveWindowToFile(hwnd);
         break;

     case IDM_ABOUT:
         WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)AboutDlg, (HMODULE) NULL,
                        IDR_ABOUTDLG, NULL );
         break;

     case IDM_REDRAW:
         fsCmd = CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ;
         break;

     case IDM_SETPELSIZE:
         {
             LONG cxFatPel, cyFatPel;

             cxFatPel = global.cxFatPel;
             cyFatPel = global.cyFatPel;

             if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)PelSizeDlg, (HMODULE) N
                            IDR_PELSIZEDLG, NULL ))
             {
                 if ((cxFatPel == global.cxFatPel) &&
                     (cyFatPel == global.cyFatPel))
                     fsCmd = CLEAR_BACKGROUND;
                 else
                     fsCmd = CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ
                 fRedraw = TRUE;
             }
         }
         break;

     case IDM_DRAGPELSIZE:
         global.fDraggingPelSize = TRUE;
         break;

     case IDM_RENDEREDOBJ:
         UPDATE_MENU_BOOL(global.fDisplayRenderedObj, IDM_RENDEREDOBJ);
         fsCmd = CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ;
         fRedraw = TRUE;
         break;

     case IDM_MATHOBJ:
         UPDATE_MENU_BOOL(global.fDisplayMathObj, IDM_MATHOBJ);
         fsCmd = CLEAR_BACKGROUND;
         fRedraw = TRUE;
         break;

     case IDM_CTLPOINTS:
         UPDATE_MENU_BOOL(global.fDisplayControlPoints, IDM_CTLPOINTS);
         fsCmd = CLEAR_BACKGROUND;
         fRedraw = TRUE;
         break;

     case IDM_CROSSHAIRS:
         UPDATE_MENU_BOOL(global.fDisplayCrossHairs, IDM_CROSSHAIRS);
         fsCmd = CLEAR_BACKGROUND;
         fRedraw = TRUE;
         break;

     case IDM_PELBORDER:
         UPDATE_MENU_BOOL(global.fDisplayPelBorder, IDM_PELBORDER);
         fsCmd = CLEAR_BACKGROUND;
         fRedraw = TRUE;
         break;

     case IDM_ROUNDPOINTS:
         UPDATE_MENU_BOOL(global.fRoundControlPoints, IDM_ROUNDPOINTS);
         fsCmd = CLEAR_BACKGROUND;
         fRedraw = TRUE;
         break;

     case IDM_AUTOREDRAW:
         UPDATE_MENU_BOOL(global.fAutoRedraw, IDM_AUTOREDRAW);
         break;

     case IDM_NOPRIM:
     case IDM_POLYLINE:
     case IDM_POLYFILLET:
     case IDM_POLYSPLINE:
     case IDM_POINTARC:
         UPDATE_MENU_LIST(global.usCurPrim, id);
         fsCmd = CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ;
         fRedraw = TRUE;
         break;

     case IDM_SETCOLORS:
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)ColorsDlg, (HMODULE) NULL,
                        IDR_COLORSDLG, NULL ))
         {
             fsCmd = CLEAR_BACKGROUND|RENDER_MATH_OBJ;
             fRedraw = TRUE;
         }
         break;

     case IDM_EDITPELCOLORS:
         UPDATE_MENU_BOOL(global.fEditPelColors, IDM_EDITPELCOLORS);
         break;

     case IDM_CLEARALL:
         global.cptl = 0L;
         fsCmd = CLEAR_BACKGROUND|CLEAR_FAT_BITMAP|RENDER_MATH_OBJ;
         fRedraw = TRUE;
         break;
     }

     if ((global.fAutoRedraw && fRedraw) || (id == IDM_REDRAW))
     {
         HPS hps;

         hps = WinGetPS(hwnd);
         global.usMix = FM_OVERPAINT;
         Paint(hps, fsCmd);
         WinReleasePS(hps);
     }
 }




 /************************************************************************
 *
 *   Paint
 *
 ************************************************************************/

 VOID
 Paint(hps, fsCmd)
 HPS  hps;
 USHORT fsCmd;
 {
     HRGN hrgn, hrgnClipOld, hrgnT;


     /* Clear the unused part of the client rectangle to a hatch pattern. */
     if (fsCmd & CLEAR_BACKGROUND)
         EraseBackground(hps);


     /* Set up the color mode as the user has requested */

     if (global.fRGB)
     {
         GpiCreateLogColorTable(hps, LCOL_RESET, LCOLF_RGB, 0L, 0L, NULL);
         if (global.hpsFat)
         {
             GpiCreateLogColorTable(global.hpsFat, LCOL_RESET, LCOLF_RGB, 0L,
             GpiCreateLogColorTable(global.hpsFatShadow, LCOL_RESET, LCOLF_RGB
         }
     }
     else
         if (global.hpsFat)
         {
             GpiCreateLogColorTable(global.hpsFat, LCOL_RESET, LCOLF_INDRGB, 0
             GpiCreateLogColorTable(global.hpsFatShadow, LCOL_RESET, LCOLF_IND
             global.clrBlackIndex = GpiQueryColorIndex(hps, 0L, 0x000000L);
         }


     if (global.usPelShape == IDD_CIRCLE)
     {
         ARCPARAMS arcp;

         arcp.lP = global.cxFatPel / 2L;
         arcp.lQ = global.cyFatPel / 2L;
         arcp.lR = 0L;
         arcp.lS = 0L;

         GpiSetArcParams(hps, &arcp);
     }


     /* set clipping rectangle to the fatbit surface */

     if ((hrgn = GpiCreateRegion(hps, 1L, &global.rcl)) != HRGN_ERROR)
         GpiSetClipRegion(hps, hrgn, &hrgnClipOld);


     if (fsCmd & CLEAR_BACKGROUND)
     {
         DrawGrid(hps);

         if (global.hpsFatShadow)
         {
             AREABUNDLE ab;

             /* clear shadow fatpel surface to background color */
             ab.lColor = global.clrField;
             GpiSetAttrs(global.hpsFatShadow, PRIM_AREA, ABB_COLOR, 0L, &ab);
             GpiBitBlt(global.hpsFatShadow, NULL, 2L, (PPOINTL)&global.rclFat,
         }
     }

     if (global.fDisplayRenderedObj && !(fsCmd & OVERRIDE_RENDERED_OBJ))
         DisplayRenderedPels(hps, fsCmd);

     if (global.fDisplayControlPoints)
     {
         /* when rubberbanding with the rendered obj, newly drawn fatpels
          * can wipe out stationary control point markers, so we have to
          * redraw them all each time
          */

         if (global.fDisplayRenderedObj || (fsCmd & CLEAR_BACKGROUND))
             DisplayControlPoints(hps, global.cptl, global.pptl, global.usMix)
         else if (global.sPtGrabbed != NO_POINT)
             /* draw just the control point that moved */
             DisplayControlPoints(hps, 1L, global.pptl+global.sPtGrabbed, glob
     }

     if (global.fDisplayMathObj)
         DisplayMathematicalObject(hps, global.usMix);

     /* delete the clip region we set up */

     if (hrgnClipOld != HRGN_ERROR)
         GpiSetClipRegion(hps, hrgnClipOld, &hrgnT);
     if (hrgn != HRGN_ERROR)
         GpiDestroyRegion(hps, hrgn);
 }




 /************************************************************************
 *
 *   DisplayMathematicalObject
 *
 ************************************************************************/

 VOID
 DisplayMathematicalObject(hps, usMix)
 HPS hps;
 USHORT usMix;
 {
     PPOINTL pptl;
     LINEBUNDLE lb;

     if (global.cptl > 0)
     {
         if (global.fRoundControlPoints)
         {
             RoundControlPoints(hps, global.cptl, global.pptl, global.pptlTmp,
                                global.cxFatPel, global.cyFatPel);
             pptl = global.pptlTmp;
         }
         else
             pptl = global.pptl;

         /* draw line */
         lb.lColor    = global.clrMathObj;
         lb.usMixMode = usMix;
         GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR|LBB_MIX_MODE, 0L, &lb);
         DrawPrimitive(hps, global.cptl, pptl);
     }
 }




 /************************************************************************
 *
 *   DisplayControlPoints
 *
 ************************************************************************/

 VOID
 DisplayControlPoints(hps, cptl, pptl, usMix)
 HPS hps;
 LONG cptl;
 PPOINTL pptl;
 USHORT usMix;
 {
     PPOINTL pptlT;
     MARKERBUNDLE mb;

     if (cptl > 0)
     {
         if (global.fRoundControlPoints)
         {
             RoundControlPoints(hps, cptl, pptl, global.pptlTmp,
                                global.cxFatPel, global.cyFatPel);
             pptlT = global.pptlTmp;
         }
         else
             pptlT = pptl;


         mb.lColor    = global.clrControlPoints;
         mb.usMixMode = usMix;
         mb.usSymbol  = global.usControlPointSymbol;
         GpiSetAttrs(hps, PRIM_MARKER, MBB_COLOR|MBB_MIX_MODE|MBB_SYMBOL, 0L,

         GpiPolyMarker(hps, cptl, pptlT);
     }
 }




 /************************************************************************
 *
 *   EraseBackground
 *
 *   Erase the unused part of the window to a hatch pattern.
 *
 ************************************************************************/

 VOID
 EraseBackground(hps)
 HPS hps;
 {
     RECTL rclClient, rclT;
     AREABUNDLE ab;


     WinQueryWindowRect(global.hwnd, &rclClient);

     ab.lColor          = CLR_BLACK;
     ab.lBackColor = CLR_WHITE;
     ab.usSymbol   = PATSYM_DIAG1;
     GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR|ABB_BACK_COLOR|ABB_SYMBOL,
                 0L, (PBUNDLE)&ab);

     if (global.rcl.yTop < rclClient.yTop)
     {
         rclT.xLeft   = rclClient.xLeft;
         rclT.yBottom = global.rcl.yBottom;
         rclT.xRight  = rclClient.xRight;
         rclT.yTop    = rclClient.yTop;
         GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, (ULONG) 0);
     }

     if (global.rcl.xRight < rclClient.xRight)
     {
         rclT.xLeft   = global.rcl.xRight;
         rclT.yBottom = rclClient.yBottom;
         rclT.xRight  = rclClient.xRight;
         rclT.yTop    = global.rcl.yTop;
         GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, (ULONG) 0);
     }

     ab.usSymbol   = PATSYM_SOLID;
     GpiSetAttrs(hps, PRIM_AREA, ABB_SYMBOL, 0L, (PBUNDLE)&ab);
 }




 /************************************************************************
 *
 *   DrawGrid
 *
 ************************************************************************/

 VOID
 DrawGrid(hps)
 HPS  hps;
 {
     AREABUNDLE ab;
     POINTL ptl;
     POINTL aptl[3];


     /* clear fatpel surface to background color */
     ab.lColor = global.clrInterstice;
     GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR, 0L, &ab);
     GpiBitBlt(hps, NULL, 2L, (PPOINTL)&global.rcl, ROP_PATCOPY, (ULONG) 0);


     /* draw one pel in lower left corner */

     ptl.x = global.cxFatPel / 2L;
     ptl.y = global.cyFatPel / 2L;
     DrawOneFatPel(hps, &ptl, global.clrField);


     /* blt up first column then across -- we don't have to worry
      * about the edges because a clip region has been setup to do that.
      */

     aptl[0].x = 0L;
     aptl[0].y = global.cyFatPel;
     aptl[1].x = global.cxFatPel;
     aptl[2].x = 0L;
     aptl[2].y = 0L;

     while (aptl[0].y <= global.rcl.yTop)
     {
         aptl[1].y  = aptl[0].y + aptl[0].y;
         GpiBitBlt(hps, hps, 3L, aptl, ROP_SRCCOPY, (LONG)NULL);
         aptl[0].y += aptl[1].y - aptl[0].y;
     }

     aptl[0].x = global.cxFatPel;
     aptl[0].y = 0L;
     aptl[1].y = global.rcl.yTop;
     aptl[2].x = 0L;
     aptl[2].y = 0L;

     while (aptl[0].x <= global.rcl.xRight)
     {
         aptl[1].x  = aptl[0].x + aptl[0].x;
         GpiBitBlt(hps, hps, 3L, aptl, ROP_SRCCOPY, (LONG)NULL);
         aptl[0].x += aptl[1].x - aptl[0].x;
     }
 }




 /************************************************************************
 *
 *   DisplayRenderedPels
 *
 ************************************************************************/

 VOID
 DisplayRenderedPels(hps, fsCmd)
 HPS hps;
 USHORT fsCmd;
 {
     LINEBUNDLE lb;
     AREABUNDLE ab;
     POINTL aptl[3];

     /*        Call GPI to draw the current primitive into the small bitmap,
      *        then fatbit it to the display.
      */

     if (global.hbmFat)
     {
         if (fsCmd & CLEAR_FAT_BITMAP)
         {
             /* clear fatpel surface to background color */
             ab.lColor = global.clrField;
             GpiSetAttrs(global.hpsFat, PRIM_AREA, ABB_COLOR, 0L, &ab);
             GpiBitBlt(global.hpsFat, NULL, 2L, (PPOINTL)&global.rclFat, ROP_P
         }

         if (fsCmd & RENDER_MATH_OBJ)
         {
             if (global.cptl > 0)
             {
                 /* draw line */
                 lb.lColor = global.clrRenderedObj;
                 GpiSetAttrs(global.hpsFat, PRIM_LINE, LBB_COLOR, 0L, &lb);
                 GpiSetModelTransformMatrix(global.hpsFat, 9L,
                                           &global.matlf, TRANSFORM_REPLACE);
                 DrawPrimitive(global.hpsFat, global.cptl, global.pptl);
                 GpiSetModelTransformMatrix(global.hpsFat, 0L, NULL, TRANSFORM
             }
         }

         /* xor the new rendered bitmap into the shadow bitmap */
         *((PRECTL)&aptl[0]) = global.rclFat;
         aptl[2].x = 0L;
         aptl[2].y = 0L;
         GpiBitBlt(global.hpsFatShadow, global.hpsFat, 3L, aptl, ROP_SRCINVERT

         /* fatbit object to the display */
         DrawFatPels(hps);

         /* get the new shadow bitmap */
         GpiBitBlt(global.hpsFatShadow, global.hpsFat, 3L, aptl, ROP_SRCCOPY,
     }
 }




 /************************************************************************
 *
 *   DrawFatPels
 *
 ************************************************************************/

 VOID
 DrawFatPels(hps)
 HPS hps;
 {
     POINTL ptl, ptlCenter;
     LONG i, j;
     COLOR clr;


     /* if the pel size is 1,1, then just blt the small bitmap to the
      * display.
      */

     if ((global.cxFatPel == 1L) && (global.cyFatPel == 1L))
     {
         POINTL aptl[3];

         *((PRECTL)&aptl[0]) = global.rcl;
         aptl[2].x = 0L;
         aptl[2].y = 0L;
         GpiBitBlt(hps, global.hpsFat, 3L, aptl, ROP_SRCCOPY, 0L);

         return;
     }

     for (i = 0; i < global.rclFat.xRight; ++i)
         for (j = 0; j < global.rclFat.yTop; ++j)
         {
             ptl.x = i;
             ptl.y = j;

             clr = GpiQueryPel(global.hpsFatShadow, &ptl);
             if ((global.fRGB && (clr != 0x000000L)) ||
                (!global.fRGB && (clr != global.clrBlackIndex)))
             {
                 clr = GpiQueryPel(global.hpsFat, &ptl);
                 ptlCenter.x = (i * global.cxFatPel) + global.cxHalfFatPel;
                 ptlCenter.y = (j * global.cyFatPel) + global.cyHalfFatPel;
                 DrawOneFatPel(hps, &ptlCenter, clr);
             }
         }
 }




 /************************************************************************
 *
 *   DrawOneFatPel
 *
 ************************************************************************/

 VOID
 DrawOneFatPel(hps, pptl, clr)
 HPS hps;
 PPOINTL pptl;
 COLOR clr;
 {
     POINTL ptl;
     LINEBUNDLE lb;
     AREABUNDLE ab;


     if (global.fDisplayPelBorder || global.fDisplayCrossHairs)
     {
         lb.lColor = global.clrCrossHair;
         GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR, 0L, &lb);
     }

     ab.lColor = clr;
     GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR, 0L, &ab);


     switch (global.usPelShape)
     {
     case IDD_SQUARE:
         {
             POINTL ptlT;
             ULONG flCmd;

             if (global.fDisplayPelBorder)
                 flCmd = DRO_OUTLINEFILL;
             else
                 flCmd = DRO_FILL;

             ptlT.x = pptl->x - global.cxHalfFatPel;
             ptlT.y = pptl->y - global.cyHalfFatPel;
             GpiSetCurrentPosition(hps, &ptlT);
             ptlT.x = pptl->x + global.cxHalfFatPel;
             ptlT.y = pptl->y + global.cyHalfFatPel;
             GpiBox(hps, flCmd, &ptlT, 0L, 0L);
         }
         break;

     case IDD_DIAMOND:
         {
             POINTL aptlT[4];
             ULONG flCmd;

             if (global.fDisplayPelBorder)
                 flCmd = BA_BOUNDARY;
             else
                 flCmd = 0L;

             aptlT[0].x = pptl->x;
             aptlT[0].y = pptl->y - global.cyHalfFatPel;
             aptlT[1].x = pptl->x - global.cxHalfFatPel;
             aptlT[1].y = pptl->y;
             aptlT[2].x = pptl->x;
             aptlT[2].y = pptl->y + global.cyHalfFatPel;
             aptlT[3].x = pptl->x + global.cxHalfFatPel;
             aptlT[3].y = pptl->y;

             GpiSetCurrentPosition(hps, &aptlT[3]);
             GpiBeginArea(hps, flCmd);
             GpiPolyLine(hps, 4L, aptlT);
             GpiEndArea(hps);
         }

         break;

     case IDD_CIRCLE:
         {
             ULONG flCmd;

             if (global.fDisplayPelBorder)
                 flCmd = DRO_OUTLINEFILL;
             else
                 flCmd = DRO_FILL;

             GpiSetCurrentPosition(hps, pptl);
             GpiFullArc(hps, flCmd, 0x10000L);
         }
         break;
     }


     if (global.fDisplayCrossHairs)
     {
         /* draw cross in center of pel */

         ptl.x = pptl->x - global.cxHalfFatPel;
         ptl.y = pptl->y;
         GpiSetCurrentPosition(hps, &ptl);
         ptl.x = pptl->x + global.cxHalfFatPel;
         GpiPolyLine(hps, 1L, &ptl);

         ptl.x = pptl->x;
         ptl.y = pptl->y - global.cyHalfFatPel;
         GpiSetCurrentPosition(hps, &ptl);
         ptl.y = pptl->y + global.cyHalfFatPel;
         GpiPolyLine(hps, 1L, &ptl);
     }
 }




 /************************************************************************
 *
 *   RoundControlPoints
 *
 ************************************************************************/

 VOID
 RoundControlPoints(hps, cptl, pptl1, pptl2, cx, cy)
 HPS hps;
 LONG cptl;
 PPOINTL pptl1;
 PPOINTL pptl2;
 LONG cx;
 LONG cy;
 {
     LONG cx2, cy2;
     LONG i;
     MATRIXLF matlf;


     /* copy the input buffer to the output/scratch buffer */
     for (i = 0; i < cptl; ++i)
         pptl2[i] = pptl1[i];


     /* set the transform, transform the points to device space (i.e. to
      * hpsFat dimensions), then restore the original transform
      */
     GpiQueryModelTransformMatrix(hps, 9L, &matlf);
     GpiSetModelTransformMatrix(hps, 9L, &global.matlf, TRANSFORM_REPLACE);
     GpiConvert(hps, CVTC_WORLD, CVTC_DEVICE, cptl, pptl2);
     GpiSetModelTransformMatrix(hps, 9L, &matlf, TRANSFORM_REPLACE);


     /* position each point in the center of its fatpel */

     cx2 = cx / 2L;
     cy2 = cy / 2L;

     for (i = 0; i < cptl; ++i, ++pptl2)
     {
         pptl2->x = pptl2->x * cx + cx2;
         pptl2->y = pptl2->y * cy + cy2;
     }
 }




 /************************************************************************
 *
 *   ComputeTransform
 *
 ************************************************************************/

 VOID
 ComputeTransform(prcl1, prcl2)
 PRECTL prcl1;
 PRECTL prcl2;
 {
     LONG xExt1, yExt1;
     LONG xExt2, yExt2;
     FIXED xScale, yScale;


     xExt1 = prcl1->xRight - prcl1->xLeft;
     yExt1 = prcl1->yTop   - prcl1->yBottom;
     xExt2 = prcl2->xRight - prcl2->xLeft;
     yExt2 = prcl2->yTop   - prcl2->yBottom;


     /* If the rectangles are of exactly the same dimensions, then
      * set the unity transform.  If not, compute the x and y scale
      * factors.  Note that in world coordinates rectangles are
      * inclusive-inclusive, whereas in device coordinates they are
      * inclusive-exclusive.  The extents of the destination are
      * therefore one pel too large as computed, so we subtract one
      * in the scale factor computation.
      */

     if (xExt1 == xExt2)
         xScale = 0x10000L;
     else
         xScale = ((xExt2-1L) * 0x10000L) / xExt1;

     if (yExt1 == yExt2)
         yScale = 0x10000L;
     else
         yScale = ((yExt2-1L) * 0x10000L) / yExt1;


     /* store the transform matrix for easy access */

     global.matlf.fxM11 = xScale;
     global.matlf.fxM12 = 0L;
     global.matlf. lM13 = 0L;
     global.matlf.fxM21 = 0L;
     global.matlf.fxM22 = yScale;
     global.matlf. lM23 = 0L;
     global.matlf. lM31 = 0L;
     global.matlf. lM32 = 0L;
     global.matlf. lM33 = 1L;
 }




 /************************************************************************
 *
 *   DrawPrimitive
 *
 ************************************************************************/

 VOID
 DrawPrimitive(hps, cptl, pptl)
 HPS hps;
 LONG cptl;
 PPOINTL pptl;
 {
     switch (global.usCurPrim)
     {
     case IDM_NOPRIM:
         break;

     case IDM_POLYLINE:
         GpiSetCurrentPosition(hps, pptl);
         GpiPolyLine(hps, cptl-1L, pptl + 1);
         break;

     case IDM_POLYFILLET:
         if (cptl >= 3L)
         {
             GpiSetCurrentPosition(hps, pptl);
             GpiPolyFillet(hps, cptl-1L, pptl + 1);
         }
         break;

     case IDM_POLYSPLINE:
         if (cptl >= 4L)
         {
             LONG cptSlack;    /* # points in pptl not usable by PolySpline */

             cptSlack = ((cptl-1L) % 3) + 1;
             GpiSetCurrentPosition( hps, pptl );
             GpiPolySpline( hps, cptl-cptSlack, pptl+1 );
         }
         break;

     case IDM_POINTARC:
         if (cptl >= 3L)
         {
             GpiSetCurrentPosition( hps, pptl );
             GpiPointArc( hps, pptl+1 );
         }
         break;
     }
 }




 /************************************************************************
 *
 *   UpdateSurfaceDims
 *
 ************************************************************************/

 VOID
 UpdateSurfaceDims()
 {
     SIZEL size;
     BITMAPINFOHEADER bminfo;
     AREABUNDLE ab;


     WinQueryWindowRect(global.hwnd, &global.rcl);

     /* compute size of small surface */
     global.rclFat.xLeft   = 0L;
     global.rclFat.yBottom = 0L;
     global.rclFat.xRight  = global.rcl.xRight / global.cxFatPel;
     global.rclFat.yTop          = global.rcl.yTop   / global.cyFatPel;

     /* compute size of fatpel version of small surface */
     global.rcl.xLeft   = 0L;
     global.rcl.yBottom = 0L;
     global.rcl.xRight  = global.rclFat.xRight * global.cxFatPel;
     global.rcl.yTop    = global.rclFat.yTop   * global.cyFatPel;

     ComputeTransform(&global.rcl, &global.rclFat);

     if ((global.rclFat.xRight <= global.rclFatBM.xRight) &&
         (global.rclFat.yTop   <= global.rclFatBM.yTop))
         return;



     /* The new fatbits surface doesn't fit in the bitmap, so we
      * have to make a new one.        If we don't have a DC or PS, make
      * those before making the bitmap.        If we do have a bitmap,
      * delete it before making the new one.
      */

     global.rclFatBM = global.rclFat;

     if (global.hbmFat)
     {
         GpiSetBitmap(global.hpsFat, NULL);
         GpiDeleteBitmap(global.hbmFat);
         GpiSetBitmap(global.hpsFatShadow, NULL);
         GpiDeleteBitmap(global.hbmFatShadow);
     }

     if (!global.hdcFat)
     {
         global.hdcFat = DevOpenDC(global.hab, OD_MEMORY, "*", 0L, NULL, NULL)
         if (!global.hdcFat)
             goto usd_error;

         global.hdcFatShadow = DevOpenDC(global.hab, OD_MEMORY, "*", 0L, NULL,
         if (!global.hdcFatShadow)
             goto usd_error;
     }

     if (!global.hpsFat)
     {
         size.cx = 0L;
         size.cy = 0L;
         global.hpsFat = GpiCreatePS(global.hab, global.hdcFat, &size,
                                  PU_PELS|GPIT_MICRO|GPIA_ASSOC);
         if (!global.hpsFat)
             goto usd_error;

         global.hpsFatShadow = GpiCreatePS(global.hab, global.hdcFatShadow, &s
                                  PU_PELS|GPIT_MICRO|GPIA_ASSOC);
         if (!global.hpsFatShadow)
             goto usd_error;
     }

     /* create bitmap with maximum color resolution (24-bit color) */
     bminfo.cbFix = sizeof(BITMAPINFOHEADER);
     bminfo.cx = (USHORT) (global.rclFatBM.xRight - global.rclFatBM.xLeft);
     bminfo.cy = (USHORT) (global.rclFatBM.yTop         - global.rclFatBM.yBot
     bminfo.cPlanes   = 1L;
     bminfo.cBitCount = 24L;
     global.hbmFat = GpiCreateBitmap(global.hpsFat, &bminfo, 0L, 0L, 0L);
     if (!global.hbmFat)
         goto usd_error;
     GpiSetBitmap(global.hpsFat, global.hbmFat);

     /* create a shadow bitmap of the one we just created */
     bminfo.cbFix = sizeof(BITMAPINFOHEADER);
     bminfo.cx = (USHORT) (global.rclFatBM.xRight - global.rclFatBM.xLeft);
     bminfo.cy = (USHORT) (global.rclFatBM.yTop         - global.rclFatBM.yBot
     bminfo.cPlanes   = 1L;
     bminfo.cBitCount = 24L;
     global.hbmFatShadow = GpiCreateBitmap(global.hpsFatShadow, &bminfo, 0L, 0
     if (!global.hbmFat)
         goto usd_error;
     GpiSetBitmap(global.hpsFatShadow, global.hbmFatShadow);

     /* clear bitmap surface to field color */
     ab.lColor = global.clrField;
     GpiSetAttrs(global.hpsFat, PRIM_AREA, ABB_COLOR, 0L, &ab);
     GpiBitBlt(global.hpsFat, NULL, 2L, (PPOINTL)&global.rclFat, ROP_PATCOPY,

     return;


 /* error exit point */

 usd_error:
     if (global.hpsFat)
         GpiDestroyPS(global.hpsFat);
     if (global.hpsFatShadow)
         GpiDestroyPS(global.hpsFatShadow);
     if (global.hdcFat)
         DevCloseDC(global.hdcFat);
     if (global.hdcFatShadow)
         DevCloseDC(global.hdcFatShadow);

     global.hpsFat        = NULL;
     global.hdcFat        = NULL;
     global.hpsFatShadow = NULL;
     global.hdcFatShadow = NULL;
 }




 /************************************************************************
 *
 *   AboutDlg
 *
 *   Process messages for the About box.
 *
 ************************************************************************/

 ULONG CALLBACK
 AboutDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (usMsg)
     {
     case WM_COMMAND:
         if (SHORT1FROMMP(mp1) == DID_OK)
             WinDismissDlg(hwnd, TRUE);
         else
             return FALSE;
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   PelSizeDlg
 *
 *   Process messages for the Pel Size dialog box.
 *
 ************************************************************************/

 ULONG CALLBACK
 PelSizeDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:
         MySetWindowLong(hwnd, IDD_PELWIDTH,  global.cxFatPel);
         MySetWindowLong(hwnd, IDD_PELHEIGHT, global.cyFatPel);
         WinSendDlgItemMsg(hwnd, global.usPelShape,
                           BM_SETCHECK, (MPARAM)TRUE, 0L);
         return FALSE;
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:
             global.cxFatPel = MyGetWindowLong(hwnd, IDD_PELWIDTH);
             global.cyFatPel = MyGetWindowLong(hwnd, IDD_PELHEIGHT);

             if (global.cxFatPel < 1L)
                 global.cxFatPel = 1L;

             if (global.cyFatPel < 1L)
                 global.cyFatPel = 1L;

             global.cxHalfFatPel = global.cxFatPel / 2L;
             global.cyHalfFatPel = global.cyFatPel / 2L;

             global.usPelShape = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_SQU
                                    BM_QUERYCHECKINDEX, 0L, 0L) + IDD_SQUARE);


             UpdateSurfaceDims();

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   ColorsDlg
 *
 *   Process messages for the Set Colors dialog box.
 *
 ************************************************************************/

 ULONG CALLBACK
 ColorsDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;
     BOOL fRGB;
     COLOR clrMathObj;
     COLOR clrRenderedObj;
     COLOR clrField;
     COLOR clrCrossHair;
     COLOR clrInterstice;
     COLOR clrControlPoints;

     switch (usMsg)
     {
     case WM_INITDLG:
         if (global.fRGB)
         {
             MySetWindowLongHex(hwnd, IDD_MATHOBJ,     global.clrMathObj);
             MySetWindowLongHex(hwnd, IDD_RENDEREDOBJ, global.clrRenderedObj);
             MySetWindowLongHex(hwnd, IDD_FIELD,             global.clrField);
             MySetWindowLongHex(hwnd, IDD_CROSSHAIRS,  global.clrCrossHair);
             MySetWindowLongHex(hwnd, IDD_INTERSTICE,  global.clrInterstice);
             MySetWindowLongHex(hwnd, IDD_CTLPOINTS,   global.clrControlPoints
         }
         else
         {
             MySetWindowLong   (hwnd, IDD_MATHOBJ,     global.clrMathObj);
             MySetWindowLong   (hwnd, IDD_RENDEREDOBJ, global.clrRenderedObj);
             MySetWindowLong   (hwnd, IDD_FIELD,             global.clrField);
             MySetWindowLong   (hwnd, IDD_CROSSHAIRS,  global.clrCrossHair);
             MySetWindowLong   (hwnd, IDD_INTERSTICE,  global.clrInterstice);
             MySetWindowLong   (hwnd, IDD_CTLPOINTS,   global.clrControlPoints
         }
         WinSendDlgItemMsg(hwnd, IDD_RGB, BM_SETCHECK, MPFROM2SHORT(global.fRG
         return FALSE;
         break;

     case WM_CONTROL:
         if ((SHORT1FROMMP(mp1) == IDD_RGB) && (SHORT2FROMMP(mp1)== BN_CLICKED
         {
             fRGB = !SHORT1FROMMR(WinSendDlgItemMsg(hwnd, IDD_RGB, BM_QUERYCHE
             WinSendDlgItemMsg(hwnd, IDD_RGB, BM_SETCHECK, MPFROM2SHORT(fRGB,0

             clrMathObj             = MyGetWindowLong(hwnd, IDD_MATHOBJ);
             clrRenderedObj   = MyGetWindowLong(hwnd, IDD_RENDEREDOBJ);
             clrField             = MyGetWindowLong(hwnd, IDD_FIELD);
             clrCrossHair     = MyGetWindowLong(hwnd, IDD_CROSSHAIRS);
             clrInterstice    = MyGetWindowLong(hwnd, IDD_INTERSTICE);
             clrControlPoints = MyGetWindowLong(hwnd, IDD_CTLPOINTS);

             if (fRGB)
             {
                 HPS hps;

                 /* for each color, get rgb value from index */

                 hps = WinGetPS(hwnd);

                 clrMathObj         = GpiQueryRGBColor(hps, 0L, clrMathObj);
                 clrRenderedObj         = GpiQueryRGBColor(hps, 0L, clrRendere
                 clrField         = GpiQueryRGBColor(hps, 0L, clrField);
                 clrCrossHair         = GpiQueryRGBColor(hps, 0L, clrCrossHair
                 clrInterstice         = GpiQueryRGBColor(hps, 0L, clrIntersti
                 clrControlPoints = GpiQueryRGBColor(hps, 0L, clrControlPoints

                 WinReleasePS(hps);

                 MySetWindowLongHex(hwnd, IDD_MATHOBJ,         clrMathObj);
                 MySetWindowLongHex(hwnd, IDD_RENDEREDOBJ, clrRenderedObj);
                 MySetWindowLongHex(hwnd, IDD_FIELD,         clrField);
                 MySetWindowLongHex(hwnd, IDD_CROSSHAIRS,  clrCrossHair);
                 MySetWindowLongHex(hwnd, IDD_INTERSTICE,  clrInterstice);
                 MySetWindowLongHex(hwnd, IDD_CTLPOINTS,         clrControlPoi
             }
             else
             {
                 HPS hps;

                 /* for each color, get nearest index value from rgb */

                 hps = WinGetPS(hwnd);

                 clrMathObj         = GpiQueryColorIndex(hps, 0L, clrMathObj);
                 clrRenderedObj         = GpiQueryColorIndex(hps, 0L, clrRende
                 clrField         = GpiQueryColorIndex(hps, 0L, clrField);
                 clrCrossHair         = GpiQueryColorIndex(hps, 0L, clrCrossHa
                 clrInterstice         = GpiQueryColorIndex(hps, 0L, clrInters
                 clrControlPoints = GpiQueryColorIndex(hps, 0L, clrControlPoin

                 WinReleasePS(hps);

                 MySetWindowLong   (hwnd, IDD_MATHOBJ,         clrMathObj);
                 MySetWindowLong   (hwnd, IDD_RENDEREDOBJ, clrRenderedObj);
                 MySetWindowLong   (hwnd, IDD_FIELD,         clrField);
                 MySetWindowLong   (hwnd, IDD_CROSSHAIRS,  clrCrossHair);
                 MySetWindowLong   (hwnd, IDD_INTERSTICE,  clrInterstice);
                 MySetWindowLong   (hwnd, IDD_CTLPOINTS,         clrControlPoi
             }
         }
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:
             global.clrMathObj            = MyGetWindowLong(hwnd, IDD_MATHOBJ)
             global.clrRenderedObj   = MyGetWindowLong(hwnd, IDD_RENDEREDOBJ);
             global.clrField            = MyGetWindowLong(hwnd, IDD_FIELD);
             global.clrCrossHair     = MyGetWindowLong(hwnd, IDD_CROSSHAIRS);
             global.clrInterstice    = MyGetWindowLong(hwnd, IDD_INTERSTICE);
             global.clrControlPoints = MyGetWindowLong(hwnd, IDD_CTLPOINTS);

             global.fRGB = SHORT1FROMMR(WinSendDlgItemMsg(hwnd, IDD_RGB, BM_QU

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   MySetWindowLong
 *
 *   Sets the given control id to the value specified.
 *
 ************************************************************************/

 VOID
 MySetWindowLong(hWnd, id, num)
 HWND hWnd;
 USHORT id;
 LONG num;
 {
     char szStr[CCHSTR];

     sprintf((NPCH)szStr, "%ld", num);
     WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
 }




 /************************************************************************
 *
 *   MySetWindowLongHex
 *
 *   Sets the given control id to the value specified, in hexadecimal
 *   notation.
 *
 ************************************************************************/

 VOID
 MySetWindowLongHex(hWnd, id, num)
 HWND hWnd;
 USHORT id;
 LONG num;
 {
     char szStr[CCHSTR];

     sprintf((NPCH)szStr, "0x%06lX", num);
     WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
 }




 /************************************************************************
 *
 *   MyGetWindowLong
 *
 *   Returns the value from the given control id.
 *
 ************************************************************************/

 LONG
 MyGetWindowLong(hWnd, id)
 HWND hWnd;
 USHORT id;
 {
     char szStr[CCHSTR];
     LONG num;

     WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);

     if (strchr(szStr, 'x'))
         sscanf((NPCH)szStr, "0x%lx", &num);
     else if (strchr(szStr, 'X'))
         sscanf((NPCH)szStr, "0X%lx", &num);
     else
         sscanf((NPCH)szStr, "%ld", &num);

     return num;
 }




 /************************************************************************
 *
 *   SaveWindowToFile
 *
 *   Copy the bits from the client rectangle (actually, just the fatpel
 *   area) into a bitmap, then save that bitmap.
 *
 ************************************************************************/

 VOID
 SaveWindowToFile(hwnd)
 HWND hwnd;
 {
     BITMAPINFOHEADER bminfo;
     HBITMAP hbm;
     HPS hps;
     POINTL aptl[3];

     /* create bitmap in display's favorite format */
     bminfo.cbFix = sizeof(BITMAPINFOHEADER);
     bminfo.cx = (USHORT) (global.rcl.xRight - global.rcl.xLeft);
     bminfo.cy = (USHORT) (global.rcl.yTop   - global.rcl.yBottom);
     bminfo.cPlanes   = 0L;
     bminfo.cBitCount = 0L;
     if (hbm = GpiCreateBitmap(global.hpsFat, &bminfo, 0L, 0L, 0L))
     {
         /* select it into the small bitmap's PS */
         GpiSetBitmap(global.hpsFat, hbm);

         /* GpiBitBlt from the window to the bitmap */
         hps = WinGetPS(hwnd);

         *((PRECTL)&aptl[0]) = global.rcl;
         aptl[2].x = 0L;
         aptl[2].y = 0L;
         GpiBitBlt(global.hpsFat, hps, 3L, aptl, ROP_SRCCOPY, 0L);

         WinReleasePS(hps);

         /* save the bitmap */
         WriteFile(hwnd, global.hpsFat);
     }

     /* deselect the bitmap and delete it */
     GpiSetBitmap(global.hpsFat, global.hbmFat);
     if (hbm)
         GpiDeleteBitmap(hbm);
 }




 /************************************************************************
 *
 *   WriteFile
 *
 *   Calls the OpenDlg's DlgFile function to ask the user what file name to
 *   save under.
 *
 ************************************************************************/

 VOID
 WriteFile(hwnd, hps)
 HWND hwnd;
 HPS hps;
 {
     HFILE hfile;
     DLF dlf;
     BITMAPINFOHEADER bmih;

     dlf.rgbAction        = DLG_SAVEDLG;
     dlf.rgbFlags        = 0;
     dlf.phFile                = &hfile;
     dlf.pszExt                = "";
     dlf.pszAppName        = "FatPel";
     dlf.pszInstructions = NULL;
     dlf.szFileName[0]        = '\0';
     dlf.szOpenFile[0]        = '\0';
     dlf.pszTitle        = "Save Bitmap";


     switch (DlgFile(hwnd,&dlf))
     {
     case TDF_ERRMEM:
     case TDF_INVALID:
         MyMessageBox(hwnd, "Error opening file.");
         break;

     case TDF_NOSAVE:
         break;

     default:
         bmih.cbFix     = sizeof(BITMAPINFOHEADER);
         bmih.cx        = (USHORT) global.rcl.xRight;
         bmih.cy        = (USHORT) global.rcl.yTop;
         bmih.cPlanes   = 0L;
         bmih.cBitCount = 0L;

         if (!WriteBMP(hfile, hps, &bmih))
             MyMessageBox(hwnd, "Error writing file.");
     }
 }




 /************************************************************************
 *
 *   WriteBMP
 *
 *   Write the bitmap out to a BMP format file.        Write the file
 *   header first, then the bitmap bits.  Space for the header
 *   and the bits is allocated.        Huge bitmaps are supported.
 *   Free up memory and close the file before leaving.  The file
 *   will have been opened by the time this function is called,
 *   and the file handle will be in the *pdlf structure.
 *
 ************************************************************************/

 BOOL
 WriteBMP(hfile, hps, pbmih)
 HFILE hfile;
 HPS hps;                 /* hps from which to get bitmap bits.           */
 PBITMAPINFOHEADER pbmih; /* Bitmap information.                    */
 {
     ULONG cScans;
     ULONG ulSize;         /* Number of bytes occupied by bitmap bits.
     USHORT cSegs;         /* Number of 64K segments in ulSize.
     USHORT cbExtra;         /* Bytes in last segment of ulSize.
     SEL selBits;         /* Base selector to bitmap bits.
     USHORT hugeshift;         /* Segment index shift value.
     USHORT cbBMHdr;         /* Size of bitmap header.
     PBITMAPFILEHEADER pbfh; /* Pointer to private copy of bitmap info data.
     USHORT cbWrite1;         /* Number of bytes to write first call to DosWri
     USHORT cbWrite2;         /* Number of bytes to write second call to DosWr
     USHORT cbWritten;         /* Number of bytes written by DosWrite.
     BOOL fRet = FALSE;         /* Function return code.
     int i;                 /* Generic loop index.
     struct
     {
         LONG cPlanes;
         LONG cBitCount;
     } bmFmt;


     /*******************************************************************
     * If the bitmap was created with either 0 planes or 0 bits per
     * pixel, then query the format to write with.  By asking for just
     * one format (two LONGs, or one instance of structure of bmFmt),
     * we'll get the device's favored format.
     *******************************************************************/

     if ((pbmih->cPlanes == 0) || (pbmih->cBitCount == 0))
     {
         if (!GpiQueryDeviceBitmapFormats(hps, 2L, (PLONG)&bmFmt))
             goto lfwrite_error_close_file;
     }
     else
     {
         bmFmt.cPlanes        = pbmih->cPlanes;
         bmFmt.cBitCount = pbmih->cBitCount;
     }


     /*******************************************************************
     * Determine size of bitmap header.        The header consists of a
     * a fixed-size part and a variable-length color table.  The
     * latter has  2^cBitCount  entries, each of which is sizeof(RGB)
     * bytes long.  The exception is when cBitCount is 24, in which
     * case the color table is omitted because the pixels are direct
     * rgb values.
     *******************************************************************/

     i = (int) bmFmt.cBitCount;
     if (i == 24)
         cbBMHdr = 0;
     else
         for (cbBMHdr = sizeof(RGB); i > 0; --i)
             cbBMHdr *= 2;
     cbBMHdr += sizeof(BITMAPFILEHEADER);


     /*******************************************************************
     * Copy structure from input to work buffer.  The call to
     * GpiQueryBitmapBits will have write-access to this, so we won't
     * let it have the user's data.
     *******************************************************************/

     pbfh = 0;
     if (DosAllocSeg(cbBMHdr, ((PUSHORT)&pbfh)+1, 0))
         goto lfwrite_error_close_file;
     pbfh->bmp = *pbmih;
     if ((pbmih->cPlanes == 0) || (pbmih->cBitCount))
     {
         pbfh->bmp.cPlanes   = (USHORT) bmFmt.cPlanes;
         pbfh->bmp.cBitCount = (USHORT) bmFmt.cBitCount;
     }


     /*******************************************************************
     * Allocate space for the bitmap bits -- all of them at once.
     * The extra ULONG casts are there to force all the arithmetic
     * to be done in 32 bits.
     *******************************************************************/

     ulSize = (
                (
                  (
                    (ULONG)pbfh->bmp.cBitCount
                    * (ULONG)pbfh->bmp.cx
                    + 31L
                  ) / 32L
                ) * (ULONG)pbfh->bmp.cPlanes * 4L
              ) * (ULONG)pbfh->bmp.cy;

     cSegs   = (USHORT)(ulSize/0x10000L);
     cbExtra = (USHORT)(ulSize%0x10000L);
     if (DosAllocHuge(cSegs, cbExtra, (PSEL)&selBits, 0, 0))
         goto lfwrite_error_free_header;
     if (DosGetHugeShift(&hugeshift))
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Tell GPI to give us the bits. The function returns the number
     * of scan lines of the bitmap that were copied.  We want all of
     * them at once.
     *******************************************************************/

     cScans = GpiQueryBitmapBits( hps
                                , 0L
                                , (ULONG)pbfh->bmp.cy
                                , (PBYTE)MAKEP(selBits, 0)
                                , (PBITMAPINFO)&pbfh->bmp);
     if (cScans != pbfh->bmp.cy)  /* compare with original number of scans */
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Fill in the extra header fields and write the header out to
     * the file.
     *******************************************************************/

     pbfh->usType    = 0x4D42;                  /* 'MB' */
     pbfh->cbSize    = ulSize + cbBMHdr;
     pbfh->xHotspot  = pbfh->bmp.cx / 2;
     pbfh->yHotspot  = pbfh->bmp.cy / 2;
     pbfh->offBits   = cbBMHdr;

     if (DosWrite( hfile
                 , (PVOID)pbfh
                 , cbBMHdr
                 , &cbWritten))
         goto lfwrite_error_free_bits;
     if (cbWritten != cbBMHdr)
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Write the bits out to the file. The DosWrite function allows a
     * maximum of 64K-1 bytes written at a time.  We get around this
     * by writing two 32K chunks for each 64K segment, and writing the
     * last segment in one piece.
     *******************************************************************/

     for (i = 0; i <= (SHORT) cSegs; ++i)
     {
         if (i < (SHORT) cSegs)
         {
             /* This segment is 64K bytes long, so split it up. */
             cbWrite1 = 0x8000;
             cbWrite2 = 0x8000;
         }
         else
         {
             /* This segment is less than 64K bytes long, so write it all. */
             cbWrite1 = cbExtra;
             cbWrite2 = 0;
         }

         /* There's a possibility that cbExtra will be 0, so check
          * to avoid an unnecessary system call.
          */
         if (cbWrite1 > 0)
         {
             if (DosWrite( hfile
                         , (PVOID)MAKEP(selBits+(i<<hugeshift), 0)
                         , cbWrite1
                         , &cbWritten))
                 goto lfwrite_error_free_bits;
             if (cbWrite1 != cbWritten)
                 goto lfwrite_error_free_bits;
         }

         /* This will always be skipped on the last partial segment. */
         if (cbWrite2 > 0)
         {
             if (DosWrite( hfile
                         , (PVOID)MAKEP(selBits+(i<<hugeshift), cbWrite1)
                         , cbWrite2
                         , &cbWritten))
                 goto lfwrite_error_free_bits;
             if (cbWrite2 != cbWritten)
                 goto lfwrite_error_free_bits;
         }
     }

     fRet = TRUE;     /* The bits are on the disk. */


     /*******************************************************************
     * Close the file, free the buffer space and leave.        This is a
     * common exit point from the function.  Since the same cleanup
     * operations need to be performed for such a large number of
     * possible error conditions, this is concise way to do the right
     * thing.
     *******************************************************************/

 lfwrite_error_free_bits:
     DosFreeSeg(selBits);
 lfwrite_error_free_header:
     DosFreeSeg(*((PUSHORT)&pbfh+1));
 lfwrite_error_close_file:
     DosClose(hfile);
     return fRet;
 }




 /************************************************************************
 *
 *   MyMessageBox
 *
 *   Displays a message box with the given string.  To simplify matters,
 *   the box will always have the same title ("FatPel"), will always
 *   have a single button ("Ok"), will always have an exclamation point
 *   icon, and will always be application modal.
 *
 ************************************************************************/

 VOID
 MyMessageBox(hWnd, sz)
 HWND hWnd;
 PSZ sz;
 {
     static char *szTitle = "FatPel Application";

     WinMessageBox(HWND_DESKTOP, hWnd, sz, szTitle, 0,
                   MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
 }


 FDIR.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\FDIR\FDIR.C

 /*************************************************************

  This sample code demonstrates conversion from longnames to 8.3 and how
  to determine the longname from the EA of a FAT file.
  Should be compiled with the Large model.

  Written by Jeff Johnson 6/20/89

  This code was written to demonstrate two new features of OS/2 V1.2:
  1) How to convert long filenames to the appropriate 8.3 names.
  2) How to find a file's longname on a FAT system by checking its EA.

  Procedures in this file:
    main()             Does the main directory program calling support modules
    Convert_to_8dot3() Converts a longname to 8.3 name
    ParsePathName()    Breaks a full path into its 3 components
    QueryLongname()    Gets the .LONGNAME EA for a given file
    QueryIFS()         Determines the IFS type of a drive

 **************************************************************/

 #define INCL_DOSFILEMGR
 #define INCL_BASE
 #include <os2.h>

 #include <string.h>
 #include <malloc.h>
 #include <stdio.h>

 #define FAT  0
 #define HPFS 1
 #define GetInfoLevel1 0x0001
 #define GetInfoLevel3 0x0003

 VOID Convert_to_8dot3(CHAR *,CHAR *);
 VOID ParsePathName(CHAR *,CHAR *,CHAR *,CHAR *);
 VOID QueryLongname(CHAR *,CHAR *);
 VOID QueryIFS(CHAR *,PUSHORT,PUSHORT);


 /*
  * Function name: main()
  *
  * Parameters:  argc, argv.  If the user places a file spec on the command
  *              line it is used to select/filter the directory listing.
  *              Otherwise, the default directory is listed.
  *
  * Returns: Always exits with 0.
  *
  * Purpose: main() coordinates the directory process by calling the
  *          appropriate setup routines, then handling the DosFindNext
  *          loop for each file.
  *
  * Usage/Warnings:  Very little error checking is done as the code is written
  *                  to demonstrate longname conversion/EA reading, not as
  *                  a finished app.
  *
  * Calls: ParsePathName(), QueryIFS(), Convert_to_8dot3(), QueryLongname()
  */

 main (int argc, char *argv[])
 {
     USHORT uRetval,hdir=0xffff,SearchCount=1;
     PFILEFINDBUF pfbuf;
     char szDrive[3],szPath[260],szFilename[CCHMAXPATH],szFullPath[CCHMAXPATH]
     USHORT ifsloc,ifsname;
     char *szFilePtr,szLongName[260];

     if(argc<2)
         ParsePathName("",szDrive,szPath,szFilename);
     else
         ParsePathName(argv[1],szDrive,szPath,szFilename);

     if(strlen(szFilename) == 0)
         strcpy(szFilename,"*");

     strcpy(szFullPath,szDrive);
     strcat(szFullPath,szPath);
     szFilePtr = szFullPath + strlen(szFullPath);
     strcat(szFullPath,szFilename);

     QueryIFS(szDrive,&ifsloc,&ifsname);

     if(ifsname != FAT && ifsname != HPFS)
     {
         printf("Unrecognized file system.\n");
         return 0;
     }

     if(ifsname == FAT)
         printf("FAT -> HPFS directory listing\n");
     else
         printf("HPFS -> FAT directory listing\n");


     pfbuf = (PFILEFINDBUF) malloc(sizeof(FILEFINDBUF));
     uRetval=DosFindFirst(szFullPath,(PHDIR) &hdir,FILE_DIRECTORY,
                          pfbuf,sizeof(FILEFINDBUF),&SearchCount,0L);

     if(uRetval)
     {
         printf("No files found.\n");
         return 0;
     }

     do
     {
         if(ifsname == FAT)
         {
             strcpy(szFilePtr,pfbuf->achName);  /* Drop in name after path */

             QueryLongname(szFullPath,szLongName);

             if(strlen(szLongName) == 0)
                 printf("%s\n",pfbuf->achName);
             else
                 printf("%s\n",szLongName);
         }
         else   /* It's HPFS */
         {
             Convert_to_8dot3(pfbuf->achName,szFilename);
             printf("%s\n",szFilename);
         }
     } while(!(uRetval=DosFindNext(hdir,pfbuf,
                                   sizeof(FILEFINDBUF),&SearchCount)));
 }


 /*
  * Function name: Convert_to_8dot3()
  *
  * Parameters:  szLong points to the input long file name.
  *              szFat points to a return buffer for the FAT compatible name.
  *
  * Returns: VOID. The converted string is placed in szFat buffer.
  *
  * Purpose: Converts a HPFS longname to the standard 8.3 name.  This is
  *          done as follows:  The extension is the first 3 characters after
  *          the last dot, no extension if there are no dots.  The file stem
  *          is at most 8 character and is taken from the beginning of the
  *          longname string, replacing all dots with underscores.
  *
  * Usage/Warnings:  Should be bulletproof.  Exception code included to allow
  *                  the special file name '..' through.
  *
  * Calls:
  */

 VOID Convert_to_8dot3(CHAR *szLong,CHAR *szFat)
 {
     USHORT usStemMaxLen; /* Holds the max size the 8 char base can be */
     USHORT cnt;
     CHAR   szStem[9],szExt[4];  /* Holds the Stem and Extension */
     CHAR   *szLastDot;          /* Pointer to the last dot */

     if(!strcmp(szLong,"..")) /* Allow the predecessor file to pass thru */
     {
         strcpy(szFat,"..");
         return;
     }

     szLastDot = strrchr(szLong,'.'); /* Find the last period */

     if(szLastDot)  /* There is at least one period */
     {
         strncpy(szExt,szLastDot+1,3);       /* 1st 3 chars after . are ext */
         szExt[3]=0;
         usStemMaxLen = szLastDot - szLong;  /* Max stem is everything b4 . */
     }
     else
     {
         *szExt = 0;                         /* No extension */
         usStemMaxLen = strlen(szLong);      /* Stem can be whole string */
     }

     if(usStemMaxLen>8)                      /* Limit stem to 8 chars */
         usStemMaxLen = 8;

     for(cnt=0;cnt<usStemMaxLen;cnt++)       /* Copy in chars to form stem */
     {
         switch(szLong[cnt])
         {
             case '.':                       /* Convert .'s to _'s */
                 szStem[cnt] = '_';
                 break;
             default:                        /* Copy all other chars */
                 szStem[cnt] = szLong[cnt];
                 break;
         }
     }
     szStem[cnt] = 0;

     /* Put it all together */
     strcpy(szFat,szStem);
     strcat(szFat,".");
     strcat(szFat,szExt);
 }


 /*
  * Function name: ParsePathName()
  *
  * Parameters:  szFullPath points to the input full path name.
  *              szDrive points to the return buffer for the drive letter.
  *              szPath points to the return buffer for the path.
  *              szFilename points to the return buffer for the Filename.
  *
  * Returns: VOID. The converted string is placed in last 3 passed params.
  *
  * Purpose: Break a full path string and break it into its three components.
  *          If the passed string doesn't have a drive, the current letter is
  *          fetched an placed in the return buffer.  The same is true for
  *          the path buffer.
  *
  * Usage/Warnings:  Error checking should be done on the DOS calls.
  *
  * Calls:
  */

 VOID ParsePathName(CHAR *szFullPath,CHAR *szDrive,CHAR *szPath,CHAR *szFilena
 {
     CHAR *szBack;          /* Used to find last backslach */
     USHORT usPathLen;      /* Holds the length of the path part of string */

     *szPath = *szFilename = 0;

     /* Do the Drive letter */
     if(*(szFullPath+1)==':')           /* If there is a drive letter */
     {
         szDrive[0] = *szFullPath;

         szFullPath += 2;
     }
     else                               /* We take the default */
     {
         USHORT dno;  /* Drive number */
         ULONG  dmap; /* Map of available drives */

         DosQCurDisk((PUSHORT) &dno,(PULONG) &dmap);
         *szDrive = (CHAR)( dno + 'A'-1);
     }
     szDrive[1] = ':';          /* Add the colon */
     szDrive[2] = (CHAR) 0;

     /* Now do the path */
     szBack = strrchr(szFullPath,'\\');
     if(szBack)                         /* There is at least 1 backslash */
     {
        usPathLen = szBack - szFullPath + 1;
        strncpy(szPath,szFullPath,usPathLen);   /* Copy path */
        szPath[usPathLen] = (CHAR) 0;
     }
     else
     {
        *szPath = (CHAR) 0;
        szBack  = szFullPath-1;  /* Points 1 char before the file name */
     }

     /* Finally do the file name */
     strcpy(szFilename,szBack+1);
 }


 /*
  * Function name: QueryLongname()
  *
  * Parameters:  szfile points to the file to be queried.
  *              szLong points to the return buffer for the long filename.
  *
  * Returns: VOID. The converted string is placed in last 3 passed params.
  *
  * Purpose: Looks for an EA named .LONGNAME attached to szfile.  If found,
  *          it places the EA value in the return buffer.
  *
  * Usage/Warnings:  Routine assumes that the EA format is LP ASCII which
  *                  is what the specs required, but probably the exception
  *                  handling should be a bit tighter.  Return buf should be
  *                  at least CCHMAXPATH long to accomodate max length names.
  *                  Note also that no check is made to prevent overwriting
  *                  the end of the return buffer.
  *
  * Calls:
  */

 VOID QueryLongname(CHAR *szfile,CHAR *szLong)
 {
     CHAR    *szEAName;  /* Points to the .LONGNAME string */
     SHORT   cbEAName;   /* Length of the .LONGNAME string */

     SHORT   cbFEAList;  /* The length of the FEA buf to be used */
     SHORT   cbGEAList;  /* "                 GEA                */

     CHAR   cvFdump[300],cvGdump[50];     /* FEA and GEA buffers */
     FEALIST *pFEAList;                   /* Pointers to the buffers */
     GEALIST *pGEAList;
     EAOP    eaop;
                                          /* Pass struct for Dos call */
     USHORT  usRetval;
     SHORT   cbRet;
     CHAR    *szT;

     *szLong = (CHAR) 0;                /* Default if we can't get the EA */

     szEAName = ".LONGNAME";        /* The particular EA we are interested in
     cbEAName = strlen(szEAName);

     cbGEAList = sizeof(GEALIST) + cbEAName;  /* Set buf lengths */
     cbFEAList = sizeof(FEALIST) + cbEAName+1 + 256 + 2;

     pFEAList = (FEALIST *) cvFdump;   /* Set pointers to 2 buffers */
     pGEAList = (GEALIST *) cvGdump;

     eaop.fpGEAList = (PGEALIST) pGEAList; /* Build the EAOP struct */
     eaop.fpFEAList = (PFEALIST) pFEAList;

     pGEAList->cbList = cbGEAList;       /* Fill in GEA buf length */
     pGEAList->list[0].cbName = (CHAR) cbEAName; /* Set .longname length */
     strcpy((char *) pGEAList->list[0].szName, (char *) szEAName);

     pFEAList -> cbList = cbFEAList; /* Set length of receiving buffer */

     usRetval = DosQPathInfo(szfile,      /* Get the .LONGNAME EA */
                             GetInfoLevel3,
                             (PVOID) &eaop,
                             sizeof(EAOP),
                             0L);

     if(usRetval != 0) /* There was an error */
        return;

     if(pFEAList->list[0].cbValue <=0)  /* It couldn't return EA value */
        return;

     szT = (CHAR *) pFEAList + sizeof(FEALIST) + cbEAName + 1;
     if (*((USHORT *) szT) == 0xfffd) /* length preceeded ASCII */
     {
         szT += 2;
         cbRet = *((USHORT *) szT);

         szT += 2;
         strncpy(szLong,szT,cbRet);
         szLong[cbRet]=0;
     }
 }


 /*
  * Function name: QueryIFS()
  *
  * Parameters:  szDrive points to drive letter to be queried.
  *              pusLocale will contain the location of the drive:
  *                        3=local, 4=remote.
  *              pusFSDName will contain the IFS type: 0=FAT, 1=HPFS, 2=Other.
  *
  * Returns: VOID.  All returns are in the last 2 params.
  *
  * Purpose: Mainly used to determine whether the file system is FAT or HPFS.
  *          also returns info on whether the drive is local or remote.
  *
  * Usage/Warnings:  Error checking on the DOS call should be implemented.
  *                  The buffer filled by DosQFSAttach is structured as follow
  *                         USHORT iType;
  *                         USHORT cbName;
  *                         UCHAR  szName[];
  *                         USHORT cbFSDName;
  *                         UCHAR  szFSDName[];
  *                         ...
  *
  * Calls:
  */

 VOID QueryIFS(CHAR *szDrive,PUSHORT pusLocale,PUSHORT pusFSDName)
 {
     CHAR vChunk[100];             /* Buffer for data from DosQFSAttach */
     USHORT usChunkLen=100;
     USHORT usTemp;                /* Holds offset for FSDName */

     DosQFSAttach(szDrive,0,GetInfoLevel1, vChunk,&usChunkLen,0L);

     *pusLocale = *((PUSHORT) vChunk);  /* Set local from 1st USHORT *);*/

     /* Skip over iType, cbName, szName, and cbFSDName fields to szFSDName */
     usTemp = *((PUSHORT) &vChunk[sizeof(USHORT)]) + sizeof(USHORT)*3 + 1;

     if(!strcmp("FAT",&vChunk[usTemp]))
     {
         *pusFSDName = 0;
         return;
     }
     if(!strcmp("HPFS",&vChunk[usTemp]))
     {
         *pusFSDName = 1;
         return;
     }

     *pusFSDName = 2;
     return;
 }



 FILE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\STOCK\FILE.C

 /***        file.C - standard file dialogs
  *
  */

 #define INCL_DOSERRORS
 #define INCL_WINCOMMON
 #define INCL_WINBUTTONS
 #define INCL_WINDIALOGS
 #define INCL_WINFRAMEMGR
 #define INCL_WININPUT
 #define INCL_WINLISTBOXES
 #define INCL_WINMENUS
 #define INCL_WINMESSAGEMGR

 #include <os2.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>

 #include "fileid.h"
 #include "file.h"
 #include "mem.h"

 #define dbg(x)        x

 typedef struct {    /* fctl */
     char    *pszTitle;                        // Dialog box title
     char    *pszPattern;                // wild-card file pattern
     char    achPath[CCHMAXPATH];        // full path of file
 } FILECTL;
 typedef FILECTL *PFILECTL;

 MRESULT EXPENTRY FileDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2);

 USHORT                beginSearch(char *psz, USHORT *pattr);
 BOOL                changeDir(HWND hwndDlg, PFILECTL pfctl);
 BOOL                changeDrive(HWND hwndDlg, PFILECTL pfctl);
 BOOL                changeFile(HWND hwndDlg, PFILECTL pfctl);
 void                endSearch(void);
 USHORT                nextSearch(char *psz, USHORT *pattr);
 USHORT                niceWinDlgBox(HWND hwndParent, HWND hwndOwner, PFNWP pf
                             HMODULE hmod, USHORT idDlg, PVOID pCreateParams);
 BOOL                setCurDir(HWND hwndDlg);
 BOOL                setDir(HWND hwndDlg);
 BOOL                setDrive(HWND hwndDlg);
 BOOL                setFile(HWND hwndDlg);
 char *                trimBlanks(char * psz);


 static BOOL        fDidOK;                        // true if OK was just pres

 static HDIR        hdir;                        // directory search handle

 static USHORT        itDrive;                // last item selected in Drive L
 static USHORT        itDir;                        // last item selected in D
 static USHORT        itFile;                        // last item selected in


 char *FileOpen(HWND hwndOwner,char *pszTitle,char *pszPattern)
 {
     BOOL    fOK;
     FILECTL fctl;

     fctl.pszTitle = pszTitle;
     fctl.pszPattern = pszPattern;

     fOK = niceWinDlgBox(HWND_DESKTOP, hwndOwner, FileDlgProc,
                             NULL, IDD_FILE, &fctl);

     if (fOK)
         return MemStrDup(fctl.achPath);
     else
         return NULL;
 }


 USHORT        niceWinDlgBox(HWND hwndParent, HWND hwndOwner, PFNWP pfnDlgProc
                             HMODULE hmod, USHORT idDlg, PVOID pCreateParams)
 {
     HAB            hab;
     HWND    hwndDlg;
     USHORT  us;
     SWP            swp;
     SWP            swpDesk;

     hwndDlg = WinLoadDlg(hwndParent, hwndOwner, pfnDlgProc,
                             hmod, idDlg, pCreateParams);

     WinQueryWindowPos(hwndDlg,&swp);  // get window position
     WinQueryWindowPos(HWND_DESKTOP,&swpDesk); // get desktop

     // center dialog box on screen

     swp.x = (swpDesk.cx - swp.cx) >> 1;
     swp.y = (swpDesk.cy - swp.cy) >> 1;

     hab = WinQueryAnchorBlock(hwndDlg);
     WinSetMultWindowPos(hab,&swp,1);

     us = WinProcessDlg(hwndDlg);
     WinDestroyWindow(hwndDlg);

     return us;
 }

 /***        FileDlgProc - "File" Dialog Procedure
 *
 */
 MRESULT EXPENTRY FileDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2)
 {
     static char            ach[CCHMAXPATH];
            USHORT   attr;
     static HWND            hwndFocus;
     static HWND            hwndLBDir;
     static HWND            hwndLBDrive;
     static HWND            hwndLBFile;
     static HWND            hwndText;
     static USHORT   idLB;
     static USHORT   idLBPrevious;
            USHORT   it;
     static PFILECTL pfctl;
            USHORT   rc;

     switch (msg) {

     case WM_INITDLG:
         pfctl = (PFILECTL)(VOID *)SHORT1FROMMP(mp2);        // set file contr

         hwndLBDir   = WinWindowFromID(hwnd, IDL_DIR);
         hwndLBDrive = WinWindowFromID(hwnd, IDL_DRIVE);
         hwndLBFile  = WinWindowFromID(hwnd, IDL_FILE);
         hwndText = WinWindowFromID(hwnd, IDC_TEXT);
         idLBPrevious = 0;                // no previous focus

         WinSetWindowText(hwnd, pfctl->pszTitle);  // set dialog title
         WinSetWindowText(hwndText, pfctl->pszPattern);        // init file pa

         setDrive(hwnd);                        // init drive list box
         setDir(hwnd);                        // update dir list
         setFile(hwnd);                        // update file list
         setCurDir(hwnd);                // init current drive/dir string

         WinDefDlgProc(hwnd, msg, mp1, mp2); // do default stuff

         hwndFocus = hwndText;
         WinSetFocus(HWND_DESKTOP,hwndFocus);
         return (MRESULT) TRUE;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {

         case DID_OK:                        // store updated list
             dbg( printf("WM_COMMAND: DID_OK\n") );
             WinQueryWindowText(hwndText, CCHMAXPATH, ach);
             trimBlanks(ach);        // Trim leading/trailing blanks
             WinSetWindowText(hwndText, ach);
             dbg( printf("   opening %s\n",ach) );
             rc = DosQFileMode(ach,&attr,0L); // Does file exist?
             dbg( printf("   DosQFileMode rc=%d\n",rc) );

             // If directory is specified, pretend file not found

             if (attr & FILE_DIRECTORY)
                 rc = ERROR_FILE_NOT_FOUND;

             fDidOK = TRUE;
             switch (rc) {

             case NO_ERROR: {
                 USHORT        cch=CCHMAXPATH;

                 rc = DosQPathInfo(
                     ach,                // Path specifiec
                     FIL_QUERYFULLNAME,        // Get fully-qualified name
                     pfctl->achPath,        // Return buffer
                     CCHMAXPATH,                // Return buffer size
                     0L                        // Reserved
                     );
                 if (rc == 0)
                     WinDismissDlg(hwnd,TRUE); // return SUCCESS
                 }
                 return FALSE;

             case ERROR_FILE_NOT_FOUND:
             case ERROR_PATH_NOT_FOUND:
                 // check for and process user wild-card pattern
                 if (strcmp(ach,pfctl->pszPattern) != 0)
                     setFile(hwnd);
                 break;                        // continue dialog

             case ERROR_ACCESS_DENIED:
             case ERROR_DRIVE_LOCKED:
             case ERROR_NOT_DOS_DISK:
                 WinMessageBox(HWND_DESKTOP, hwnd, "Access Denied",
                               "Error", NULL, MB_ICONEXCLAMATION);
                 break;

             default:
                 break;
             }
             WinSetFocus(HWND_DESKTOP,hwndFocus);
             break;

         case DID_CANCEL:
             WinDismissDlg(hwnd, NULL);
             break;
         }
         break;

     case WM_CONTROL:
         idLBPrevious = idLB;                // remember previous focus
         idLB = SHORT1FROMMP(mp1);
         dbg (printf("WM_CONTROL: id = %04x hwndLB=%08x\n",idLB,mp2) );
         switch (SHORT2FROMMP(mp1)) {
             case LN_ENTER:
                 switch (idLB) {

                 case IDL_DRIVE:
                     dbg( printf("LN_ENTER: IDL_DRIVE\n") );
                     changeDrive(hwnd,pfctl);
                     return FALSE;

                 case IDL_DIR:
                     dbg( printf("LN_ENTER: IDL_DIR\n") );
                     changeDir(hwnd,pfctl);
                     return FALSE;

                 case IDL_FILE:
                     dbg( printf("LN_ENTER: IDL_FILE\n") );
                     changeFile(hwnd,pfctl);
                     WinSendMsg(hwnd,WM_COMMAND,MPFROMSHORT(DID_OK),NULL);
                     return FALSE;

                 default:
                     dbg( printf("LN_ENTER: unknown list box = %04x\n",idLB) )
                     return WinDefDlgProc(hwnd, msg, mp1, mp2);
                     break;
                 }
                 break;

             case LN_SELECT:
                 switch (idLB) {

                 case IDL_DRIVE:
                     dbg( printf("LN_SELECT: IDL_DRIVE\n") );
                     it = SHORT1FROMMR(WinSendMsg(hwndFocus, LM_QUERYSELECTION
                     if (it != LIT_NONE) {
                         if (fDidOK)
                             fDidOK = FALSE;
                         else {
                             itDrive = it;
                             WinSendMsg(hwndFocus, LM_QUERYITEMTEXT,
                                 MPFROM2SHORT(it,CCHMAXPATH), MPFROMP(ach));
                             strcat(ach,pfctl->pszPattern);
                             WinSetWindowText(hwndText, ach);
                         }
                     }
                     idLB = 0;                // allow LN_SETFOCUS to work
                     return FALSE;

                 case IDL_DIR:
                     dbg( printf("LN_SELECT: IDL_DIR\n") );
                     it = SHORT1FROMMR(WinSendMsg(hwndFocus, LM_QUERYSELECTION
                     if (it != LIT_NONE) {
                         if (fDidOK)
                             fDidOK = FALSE;
                         else {
                             itDir = it;
                             WinSendMsg(hwndFocus, LM_QUERYITEMTEXT,
                                 MPFROM2SHORT(it,CCHMAXPATH), MPFROMP(ach));
                             strcat(ach,"\\");
                             strcat(ach,pfctl->pszPattern);
                             WinSetWindowText(hwndText, ach);
                         }
                     }
                     idLB = 0;                // allow LN_SETFOCUS to work
                     return FALSE;

                 case IDL_FILE:
                     dbg( printf("LN_SELECT: IDL_FILE\n") );
                     it = SHORT1FROMMR(WinSendMsg(hwndFocus, LM_QUERYSELECTION
                     if (it != LIT_NONE) {
                         if (fDidOK)
                             fDidOK = FALSE;
                         else {
                             itFile = it;
                             changeFile(hwnd,pfctl);
                         }
                     }
                     idLB = 0;                // allow LN_SETFOCUS to work
                     return FALSE;

                 default:
                     dbg( printf("LN_SELECT: unknown list box = %04x\n",idLB)
                     return WinDefDlgProc(hwnd, msg, mp1, mp2);
                     break;
                 }
                 break;

             case LN_SETFOCUS:
                 //* The following test prevents reselecting the last
                 //  selected list box item when the user is scrolling
                 //  the list box.

                 if (idLB == idLBPrevious)
                     break;                // no actual focus change

                 switch (idLB) {

                 case IDL_DRIVE:
                     dbg( printf("LN_SETFOCUS: IDL_DRIVE\n") );
                     hwndFocus = hwndLBDrive;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(itDrive), MPFROMSHORT(TRUE));
                     return FALSE;

                 case IDL_DIR:
                     dbg( printf("LN_SETFOCUS: IDL_DIR\n") );
                     hwndFocus = hwndLBDir;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(itDir), MPFROMSHORT(TRUE));
                     return FALSE;

                 case IDL_FILE:
                     dbg( printf("LN_SETFOCUS: IDL_FILE\n") );
                     hwndFocus = hwndLBFile;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(itFile), MPFROMSHORT(TRUE));
                     return FALSE;

                 default:
                     dbg( printf("LN_SETFOCUS: unknown list box = %04x\n",idLB
                     return WinDefDlgProc(hwnd, msg, mp1, mp2);
                     break;
                 }
                 break;

             case LN_KILLFOCUS:
                 switch (idLB) {

                 case IDL_DRIVE:
                     dbg( printf("LN_KILLFOCUS: IDL_DRIVE\n") );
                     hwndFocus = hwndLBDrive;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(LIT_NONE), MPFROMSHORT(FALSE));
                     return FALSE;

                 case IDL_DIR:
                     dbg( printf("LN_KILLFOCUS: IDL_DIR\n") );
                     hwndFocus = hwndLBDir;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(LIT_NONE), MPFROMSHORT(FALSE));
                     return FALSE;

                 case IDL_FILE:
                     dbg( printf("LN_KILLFOCUS: IDL_FILE\n") );
                     hwndFocus = hwndLBFile;
                     WinSendMsg(hwndFocus, LM_SELECTITEM,
                             MPFROMSHORT(LIT_NONE), MPFROMSHORT(FALSE));
                     return FALSE;

                 default:
                     dbg( printf("LN_KILLFOCUS: unknown list box = %04x\n",idL
                     return WinDefDlgProc(hwnd, msg, mp1, mp2);
                     break;
                 }
                 break;

             default:
                 dbg( printf("notify code= %04x\n",SHORT2FROMMP(mp1)) );
                 return WinDefDlgProc(hwnd, msg, mp1, mp2);
         }
         return FALSE;

     default:
         return WinDefDlgProc(hwnd, msg, mp1, mp2);
     }
     return FALSE;
 }


 BOOL        changeDrive(HWND hwndDlg, PFILECTL pfctl)
 {
     USHORT  it;
     USHORT  drv;
     HWND    hwndLBDrive;
     HWND    hwndText;

     hwndLBDrive = WinWindowFromID(hwndDlg, IDL_DRIVE);
     hwndText = WinWindowFromID(hwndDlg, IDC_TEXT);

     it = SHORT1FROMMR(WinSendMsg(hwndLBDrive, LM_QUERYSELECTION, 0L, 0L));
     if (it != LIT_NONE) {
         itDrive = it;
         drv = SHORT1FROMMR(WinSendMsg(hwndLBDrive, // get drive number
                         LM_QUERYITEMHANDLE, MPFROMSHORT(it), 0L));
         DosSelectDisk(drv);                // change drive
         WinSetWindowText(hwndText, pfctl->pszPattern);        // reset patter
         setDir(hwndDlg);                // update dir list
         setFile(hwndDlg);                // update file list
         setCurDir(hwndDlg);                // update current drive/dir text
         return TRUE;
     }
     return FALSE;
 }


 BOOL        changeDir(HWND hwndDlg, PFILECTL pfctl)
 {
     char    ach[CCHMAXPATH];
     HWND    hwndLBDir;
     HWND    hwndText;
     USHORT  it;

     hwndLBDir = WinWindowFromID(hwndDlg, IDL_DIR);
     hwndText = WinWindowFromID(hwndDlg, IDC_TEXT);

     it = SHORT1FROMMR(WinSendMsg(hwndLBDir, LM_QUERYSELECTION, 0L, 0L));
     if (it != LIT_NONE) {
         itDir = it;
         WinSendMsg(hwndLBDir, LM_QUERYITEMTEXT, // get dir
                         MPFROM2SHORT(it,CCHMAXPATH), MPFROMP(ach));
         DosChDir(ach, 0L);
         WinSetWindowText(hwndText, pfctl->pszPattern);        // reset patter
         setDir(hwndDlg);                // update dir list
         setFile(hwndDlg);                // update file list
         setCurDir(hwndDlg);                // update current drive/dir text
         return TRUE;
     }
     return FALSE;
 }

 BOOL        changeFile(HWND hwndDlg, PFILECTL pfctl)
 {
     char    ach[CCHMAXPATH];
     HWND    hwndLBFile;
     HWND    hwndText;
     USHORT  it;

     hwndText = WinWindowFromID(hwndDlg, IDC_TEXT);
     hwndLBFile = WinWindowFromID(hwndDlg, IDL_FILE);

     it = SHORT1FROMMR(WinSendMsg(hwndLBFile, LM_QUERYSELECTION, 0L, 0L));
     if (it != LIT_NONE) {
         itFile = it;
         WinSendMsg(hwndLBFile, LM_QUERYITEMTEXT, // get dir
                         MPFROM2SHORT(it,CCHMAXPATH), MPFROMP(ach));
         WinSetWindowText(hwndText, ach);  // set file name
         return TRUE;
     }
     return FALSE;
 }

 BOOL        setDrive(HWND hwndDlg)
 {
     char    ach[3];
     ULONG   bmlDrives;
     USHORT  drvCurrent;
     HWND    hwndLBDrive;
     USHORT  i;
     USHORT  us;


     hwndLBDrive = WinWindowFromID(hwndDlg, IDL_DRIVE);

     if (DosQCurDisk(&drvCurrent,&bmlDrives) != 0) {
         dbg( printf("DosQCurDisk failed!\n") );
         return FALSE;
     }
     drvCurrent--;                        // 0-based drive number

     ach[1] = ':';                        // init drive string
     ach[2] = '\0';

     WinEnableWindowUpdate(hwndLBDrive,FALSE); // turn off list box updates
     WinSendMsg(hwndLBDrive, LM_DELETEALL, NULL, NULL); // delete old entries

     for (i=0; bmlDrives != NULL; i++) { // get all 1 bits!
         if (bmlDrives & 1) {                // drive exists
             ach[0] = (char)(i + 'a');
             us = SHORT1FROMMR(WinSendMsg(hwndLBDrive, LM_INSERTITEM,
                     MPFROMSHORT(LIT_SORTASCENDING), MPFROMP(ach)));
             WinSendMsg(hwndLBDrive, LM_SETITEMHANDLE,         // set drive nu
                 MPFROMSHORT(us), MPFROMSHORT(i+1));
             if (i == drvCurrent) {
                 itDrive = us;                // save index for selection
             }
         }
         bmlDrives >>= 1;                // get next drive bit in bit 0
     }

     WinEnableWindowUpdate(hwndLBDrive,TRUE); // repaint list box
     return TRUE;
 }

 BOOL        setCurDir(HWND hwndDlg)
 {
     char    ach[CCHMAXPATH];
     ULONG   bmlDrives;
     USHORT  cch;
     USHORT  drvCurrent;
     HWND    hwndCurDir;
     USHORT  rc;

     rc = DosQCurDisk(&drvCurrent,&bmlDrives);        // get current drive

     ach[0] = (char)((char)drvCurrent + 'a' - (char)1) ; // make drive letter
     ach[1] = ':';                        // rest of drive string
     ach[2] = '\\';

     cch = CCHMAXPATH-3;                            // room for drive string

     rc = DosQCurDir(drvCurrent, &ach[3], &cch);

     hwndCurDir = WinWindowFromID(hwndDlg, IDC_CURDIR);
     WinSetWindowText(hwndCurDir,ach);        // set current drive/dir text
     return TRUE;
 }


 BOOL        setDir(HWND hwndDlg)
 {
     char            ach[20];
     static USHORT   attr;
     USHORT            cch;
     HWND            hwndLBDir;

     hwndLBDir  = WinWindowFromID(hwndDlg, IDL_DIR);

     WinEnableWindowUpdate(hwndLBDir,FALSE); // turn off list box updates
     WinSendMsg(hwndLBDir, LM_DELETEALL, NULL, NULL); // delete old entries

     // get all directories

     strcpy(ach,"*.*");
     attr = 0x37;
     for (cch = beginSearch(ach,&attr);
          cch != 0;
          cch = nextSearch(ach,&attr)) {
         if (attr & 0x0010) {                // only get directories
         //  if ((cch > 1) || (ach[0] != '.')) { // do all but "."
                 WinSendMsg(hwndLBDir, LM_INSERTITEM,
                     MPFROMSHORT(LIT_SORTASCENDING), MPFROMP(ach));
         //  }
         }
     }
     endSearch();

     WinEnableWindowUpdate(hwndLBDir,TRUE); // repaint list box
     itDir = 1;
     return TRUE;
 }


 BOOL        setFile(HWND hwndDlg)
 {
     char            ach[20];
     static USHORT   attr;
     USHORT            cch;
     HWND            hwndLBFile;
     HWND            hwndText;

     hwndLBFile = WinWindowFromID(hwndDlg, IDL_FILE);
     hwndText   = WinWindowFromID(hwndDlg, IDC_TEXT);

     WinEnableWindowUpdate(hwndLBFile,FALSE); // turn off list box updates
     WinSendMsg(hwndLBFile, LM_DELETEALL, NULL, NULL); // delete old entries

     // get only files that match user's pattern

     WinQueryWindowText(hwndText,CCHMAXPATH,ach);
     dbg( printf("file pattern = %s\n",ach) );
     attr = 0x27;                        // do all but directories
     for (cch = beginSearch(ach,&attr);
          cch != 0;
          cch = nextSearch(ach,&attr) ) {
         WinSendMsg(hwndLBFile, LM_INSERTITEM,
                 MPFROMSHORT(LIT_SORTASCENDING), MPFROMP(ach));

     }
     endSearch();
     WinEnableWindowUpdate(hwndLBFile,TRUE); // repaint list box
     itFile = 1;                            // default selection

     return TRUE;
 }


 USHORT        beginSearch(char *psz, USHORT *pattr)
 {
     FILEFINDBUF            findbuf;
     USHORT            cf;
     USHORT            rc;

     cf = 1;                            // only return 1 file
     hdir = 0xFFFF;                    // create a search handle

     rc = DosFindFirst(psz,            // file specification
                       &hdir,            // pointer to variable for handle
                       *pattr,            // search attribute (everything)
                       &findbuf,            // pointer to result buffer
                       sizeof(FILEFINDBUF), // length of result buffer
                       &cf,            // files to find/files found
                       0L);            // Must be zero

     if (cf != 0) {
         psz[findbuf.cchName] = '\0';        // terminate name
         strncpy(psz,findbuf.achName,findbuf.cchName); // copy name to caller
         *pattr = findbuf.attrFile;
         return findbuf.cchName;
     }
     return 0;
 }


 USHORT        nextSearch(char *psz, USHORT *pattr)
 {
     FILEFINDBUF            findbuf;
     USHORT            cf;
     USHORT            rc;

     cf = 1;            // only return 1 file

     rc = DosFindNext(hdir,            // pointer to variable for handle
                      &findbuf,            // pointer to result buffer
                      sizeof(FILEFINDBUF), // length of result buffer
                      &cf);            // files to find/files found

     if (cf != 0) {
         psz[findbuf.cchName] = '\0'; // terminate name
         strncpy(psz,findbuf.achName,findbuf.cchName); // copy name to caller
         *pattr = findbuf.attrFile;
         return findbuf.cchName;
     }
     return 0;
 }


 void        endSearch(void)
 {
     DosFindClose(hdir);
 }


 /***        trimBlanks - trim off leading and trailing blanks
 *
 *        ENTRY        psz - string to be trimmed
 */

 char * trimBlanks(char *psz)
 {
     char *pch;
     char *pchDst;
     char *pchRight;

     //        Find right-most non-blank character

     for (pch = psz+strlen(psz)-1;        // start at last character
          (pch >= psz) && (*pch == ' '); // scan backward to non-blank
         pch--)
             ;

     *(pch+1) = '\0';                        // trim trailing blanks

     pchRight = pch;

     // Find left-most non-blank character

     for (pch = psz;                        // start at first character
          (pch <= pchRight) && (*pch == ' '); // scan forward to non-blank
          pch++)
             ;

     // Shift string left to trim leading blanks

     if (pch > psz) {                        // leading blanks to trim
         pchRight++;                        // grab traling null
         pchDst = psz;                        // do not destroy psz
         while (pchDst <= pchRight)
             *pchDst++ = *pch++;                // shift string left
     }
     return psz;
 }


 FILE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\FILE.C

 /*
     FILE.C -- Open/Save As Dialog Box library routines
     Created by Microsoft Corporation, 1989
 */
 #define  INCL_DOS
 #define  INCL_DOSQUEUES
 #include "tool.h"


 /****************************************************************************
 * This function is the Open dialog box window procedure.  It handles input,
 * allows the user to change directories, checks for legal filenames, opens
 * specified files, appends default extensions and returns the file's name.
 *
 * The return values are
 *   TDF_INVALID - Library error (internal error),
 *   TDF_NOOPEN  - User hits cancel
 *   TDF_NEWOPEN - Created new file (file left open)
 *   TDF_OLDOPEN - Opened existing file (file left open)
 \****************************************************************************

 MRESULT EXPENTRY DlgOpenWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
     {
     PDLF pdlf;
     PSZ  lpchFile;
     CHAR sz[MAX_FNAME_LEN];

     switch (msg)
         {
     case WM_INITDLG:
         /* initialize dialog box */
         DlgInitOpen(hwnd, LONGFROMMP(mp2));

         /* fill static field with path name and fill list box with
            filenames that lMatch spec */
         if (!DlgDirList(hwnd, ((PDLF)(mp2))->pszExt+1, ID_DIRLIST,
                         ID_FILELIST, ID_PATH, ((PDLF)(mp2))->rgbFlags))
             /* NOTE: shouldn't we post a message if something screws up? */
             WinDismissDlg(hwnd, TDF_INVALID);
         break;

     case WM_COMMAND:
         pdlf = (PDLF) WinQueryWindowULong(hwnd, 0);
         switch (SHORT1FROMMP(mp1))
             {
         case MBID_OK:
             /* Open button pressed */
             /* get name from edit box */
             WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                 CBROOTNAMEMAX, (PSZ)pdlf->szFileName);
             Upper((PSZ)pdlf->szFileName);
             if (lstrlen((PSZ)pdlf->szFileName))
                 DlgOpenName(hwnd, pdlf);
             break;

         case MBID_CANCEL:
             /* Cancel button pressed, dismiss dialog box */
             WinDismissDlg(hwnd, TDF_NOOPEN);
             break;

         case MBID_HELP:
             /* Help button pressed */
             WinMessageBox( HWND_DESKTOP
                          , hwnd
                          , pdlf->pszInstructions
                          , pdlf->pszTitle
                          , NULL
                          , MB_OK | MB_APPLMODAL );
             break;
             }
         break;

     case WM_CONTROL:
         pdlf = (PDLF) WinQueryWindowULong(hwnd, 0);
         switch (SHORT1FROMMP(mp1))
             {
         case ID_DIRLIST:
             /* user clicked in directory list box */
             switch (SHORT2FROMMP(mp1))
                 {
             case LN_SELECT:
                 /* single click case */
                 /* get current edit string */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)sz);
                 Upper((PSZ)sz);

                 /* get selected string */
                 if (DlgDirSelect(hwnd, (PSZ)pdlf->szFileName, ID_DIRLIST))
                     {
                     /* if edit field contains wild card, then append file
                        part to selected directory, otherwise append
                        last wildcard search spec */
                     lpchFile = FileInPath((PSZ)sz);
                     lstrcat( (PSZ)pdlf->szFileName
                            , DlgSearchSpec( lpchFile)
                              ? lpchFile
                              : (PSZ)pdlf->szLastWild );
                     /* set edit box to resulting name */
                     WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                      (PSZ)pdlf->szFileName);
                     }
                 break;

             case LN_ENTER:
                 /* get text from edit box */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)pdlf->szFileName);
                 Upper((PSZ)pdlf->szFileName);
                 if( DlgSearchSpec( (PSZ)pdlf->szFileName))
                     {
                     DlgDirList( hwnd
                               , (PSZ)pdlf->szFileName
                               , ID_DIRLIST
                               , ID_FILELIST
                               , ID_PATH
                               , pdlf->rgbFlags );
                     lstrcpy( (PSZ)pdlf->szLastWild
                            , FileInPath( (PSZ)pdlf->szFileName));
                     WinSetWindowText( WinWindowFromID( hwnd, ID_EDIT)
                                     , (PSZ)pdlf->szFileName );
                     }
                 break;
                 }
             break;

         case ID_FILELIST:
             /* user clicked in file list box */
             switch (SHORT2FROMMP(mp1))
                 {
             case LN_SELECT:
                 /* single click case */

                 /* get current edit string */
                 /* if it contains a wildcard, save it before obliteration */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)sz);
                 Upper((PSZ)sz);
                 if( DlgSearchSpec( (PSZ)sz))
                     lstrcpy( (PSZ)pdlf->szLastWild, FileInPath( (PSZ)sz));

                 /* get selected file name */
                 DlgDirSelect(hwnd, (PSZ)pdlf->szFileName, ID_FILELIST);
                 /* set edit box to resulting name */
                 WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                  (PSZ)pdlf->szFileName);
                 break;

             case LN_ENTER:
                 /* double click case, single click already processed */
                 DlgOpenName(hwnd, pdlf);
                 break;
                 }
             break;
             }
         break;

     default:
         return (WinDefDlgProc(hwnd, msg, mp1, mp2));
         }

     return (MRFROMLONG(0L));  /* message processed */
     }




 /****************************************************************************
 * This function stores the pdlf in the dialog window structure, sets
 * the title and instruction texts and limits the text size.
 \****************************************************************************

 VOID PASCAL DlgInitOpen (hwnd, lpParm)
 HWND hwnd;
 ULONG lpParm;
     {
     PDLF pdlf;

     /* Set pdlf local to window */
     pdlf = (PDLF) lpParm;
     WinSetWindowULong(hwnd, 0, lpParm);

     /* set edit box text size limit */
     WinSendDlgItemMsg(hwnd, ID_EDIT, EM_SETTEXTLIMIT, (MPARAM)CBROOTNAMEMAX,

     /* set edit window to search spec */
     if (pdlf->pszExt != (PSZ)NULL)
         WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT), pdlf->pszExt+1);

     /* set title window */
     if (pdlf->pszTitle != (PSZ)NULL)
         WinSetWindowText(WinWindowFromID(hwnd, FID_TITLEBAR), pdlf->pszTitle)
     }




 /****************************************************************************
 * This function processes the currently selected name in the open dialog
 * box.  If the name represents a directory, the current directory is
 * changed to that one and the corresponding files are displayed.  If the
 * name is a file, then it is opened.
 \****************************************************************************

 VOID PASCAL DlgOpenName(hwnd, pdlf)
 HWND hwnd;
 PDLF pdlf;
     {
     PSZ  lpch;
     USHORT wVal;
     CHAR sz[MAX_FNAME_LEN];

     /* try using current name as a directory */
     lstrcpy((PSZ)sz, (PSZ)pdlf->szFileName);
     if (!DlgSearchSpec((PSZ)sz))
         DlgAddSearchExt(pdlf, (PSZ)sz);
     if (DlgDirList(hwnd, (PSZ)sz, ID_DIRLIST, ID_FILELIST, ID_PATH,
                    pdlf->rgbFlags))
         {
         /* name was a directory, extract and set file name */
         lpch = FileInPath((PSZ)sz);
         lstrcpy((PSZ)pdlf->szFileName, lpch);
         WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT),
                          (PSZ)pdlf->szFileName);
         }
     else
         /* try to open name as a file */
         if ((wVal = DlgOpenFile(pdlf, hwnd)) != TDF_NOOPEN)
             WinDismissDlg(hwnd, wVal);
     }




 /****************************************************************************
 * This function is the SaveAs dialog box window procedure.  It handles input,
 * tests for legal filenames and uses message boxes to report any problems.
 *
 * Return values are:
 *     TDF_INVALID - Library error (internal error),
 *     TDF_NOOPEN  - User hits cancel
 *   Specific for DLG_NOOPEN
 *     TDF_NEWSAVE - user wants to save to a new file (file not created)
 *     TDF_OLDSAVE - user wants to save over existing file (file not opened)
 *   else
 *     TDF_NEWSAVE - user wants to save to a new file (file left open)
 *     TDF_OLDSAVE - user wants to save over existing file (file left open)
 \****************************************************************************

 MRESULT EXPENTRY DlgSaveAsWndProc(hwnd, msg, mp1, mp2)
 HWND   hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
     {
     PDLF    pdlf;
     PSZ     lpchFile;
     CHAR    sz[MAX_FNAME_LEN];

     switch (msg)
         {
     case WM_INITDLG:
         /* Store pdlf, set instructions, limit text size */
         DlgInitSaveAs(hwnd, LONGFROMMP(mp2));

         /* fill static field with path name and fill list box with
            filenames that lMatch spec */
         if (!DlgDirList(hwnd, ((PDLF)(mp2))->pszExt+1, ID_DIRLIST,
                         ID_FILELIST, ID_PATH, ((PDLF)(mp2))->rgbFlags))
             /* NOTE: shouldn't we post a message if something screws up? */
             WinDismissDlg(hwnd, TDF_INVALID);
         break;

     case WM_COMMAND:
         pdlf = (PDLF) WinQueryWindowULong(hwnd, 0);
         switch (SHORT1FROMMP(mp1))
            {
         case MBID_OK:
             /* get text from edit box */
             WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                CBROOTNAMEMAX, (PSZ)pdlf->szFileName);
             Upper((PSZ)pdlf->szFileName);
             if( DlgSearchSpec( (PSZ)pdlf->szFileName))
                 {
                 DlgDirList( hwnd
                           , (PSZ)pdlf->szFileName
                           , ID_DIRLIST
                           , ID_FILELIST
                           , ID_PATH
                           , pdlf->rgbFlags );
                 lstrcpy( (PSZ)pdlf->szLastWild
                        , FileInPath( (PSZ)pdlf->szFileName));
                 lstrcpy( (PSZ)pdlf->szFileName, (PSZ)pdlf->szLastFile);
                 WinSetWindowText( WinWindowFromID( hwnd, ID_EDIT)
                                 , (PSZ)pdlf->szFileName );
                 }
             else if( lstrlen( (PSZ)pdlf->szFileName))
                 DlgSaveAsName( hwnd, pdlf);
             break;

         case MBID_CANCEL:
             WinDismissDlg(hwnd, TDF_NOSAVE);
             break;

         case MBID_HELP:
             /* Help button pressed */
             WinMessageBox( HWND_DESKTOP
                          , hwnd
                          , pdlf->pszInstructions
                          , pdlf->pszTitle
                          , NULL
                          , MB_OK | MB_APPLMODAL );
             break;
             }
         break;

     case WM_CONTROL:
         pdlf = (PDLF) WinQueryWindowULong(hwnd, 0);
         switch (SHORT1FROMMP(mp1))
             {
         case ID_DIRLIST:
             /* user clicked in directory list box */
             switch (SHORT2FROMMP(mp1))
                 {
             case LN_SELECT:
                 /* single click case */
                 /* get current edit string */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)sz);
                 Upper((PSZ)sz);

                 /* get selected string */
                 if (DlgDirSelect(hwnd, (PSZ)pdlf->szFileName, ID_DIRLIST))
                     {
                     /* if edit field contains wild card, then append file
                        part to selected directory, otherwise append
                        last wildcard search spec */
                     lpchFile = FileInPath((PSZ)sz);
                     if( DlgSearchSpec( lpchFile))
                         {
                         lstrcat( (PSZ)pdlf->szFileName, lpchFile );
                         lstrcpy( (PSZ)pdlf->szLastWild, lpchFile);
                         }
                     else
                         {
                         lstrcat( (PSZ)pdlf->szFileName, (PSZ)pdlf->szLastWild
                         lstrcpy( (PSZ)pdlf->szLastFile, lpchFile);
                         }
                     /* set edit box to resulting name */
                     WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                      (PSZ)pdlf->szFileName);
                     }
                 break;

             case LN_ENTER:
                 /* get text from edit box */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)pdlf->szFileName);
                 Upper((PSZ)pdlf->szFileName);
                 if( DlgSearchSpec( (PSZ)pdlf->szFileName))
                     {
                     DlgDirList( hwnd
                               , (PSZ)pdlf->szFileName
                               , ID_DIRLIST
                               , ID_FILELIST
                               , ID_PATH
                               , pdlf->rgbFlags );
                     lstrcpy( (PSZ)pdlf->szLastWild
                            , FileInPath( (PSZ)pdlf->szFileName));
                     lstrcpy( (PSZ)pdlf->szFileName, (PSZ)pdlf->szLastFile);
                     WinSetWindowText( WinWindowFromID( hwnd, ID_EDIT)
                                     , (PSZ)pdlf->szFileName );
                     }
                 break;
                 }
             break;

         case ID_FILELIST:
             /* user clicked in file list box */
             switch (SHORT2FROMMP(mp1))
                 {
             case LN_SELECT:
                 /* single click case */

                 /* get current edit string */
                 /* if it contains a wildcard, save it before obliteration */
                 WinQueryWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                    CBROOTNAMEMAX, (PSZ)sz);
                 Upper((PSZ)sz);
                 if( DlgSearchSpec( (PSZ)sz))
                     lstrcpy( (PSZ)pdlf->szLastWild, FileInPath( (PSZ)sz));

                 /* get selected file name */
                 DlgDirSelect(hwnd, (PSZ)pdlf->szFileName, ID_FILELIST);
                 /* set edit box to resulting name */
                 WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT),
                                  (PSZ)pdlf->szFileName);
                 break;

             case LN_ENTER:
                 /* double click case, single click already processed */
                 DlgSaveAsName(hwnd, pdlf);
                 break;
                 }
             break;
             }
         break;

     default:
         return (WinDefDlgProc(hwnd, msg, mp1, mp2));
         }

     return (MRFROMLONG(0L));    /* message processed */
     }



 /****************************************************************************
 /*
 /* This function attempts to open a file for writing.  It queries if over-
 /* write is OK if the file already exists.
 /*
 /****************************************************************************
 VOID PASCAL DlgSaveAsName( hwnd, pdlf)
 HWND  hwnd;
 PDLF  pdlf;
     {
     USHORT  usTdf;

     /* add extension if there is not one already */
     AddExt((PSZ)pdlf->szFileName, pdlf->pszExt);
     /* test file name legality */
     if (!DlgParseFile((PSZ)pdlf->szFileName,
                       (PSZ)pdlf->szOpenFile, FALSE, FALSE))
         {
         /* illegal filename */
         DlgAlertBox(hwnd, IDS_IFN, pdlf,
                     MB_OK | MB_ICONEXCLAMATION);
         return;
         }
     usTdf = TDF_NEWSAVE;
     /* test if file already exists */
     if (DlgParseFile((PSZ)pdlf->szFileName,
                      (PSZ)pdlf->szOpenFile, TRUE, FALSE))
         {
         /* overwrite? */
         if (DlgAlertBox(hwnd, IDS_REF, pdlf,
                         MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION)
             == MBID_NO)
             return;
         usTdf = TDF_OLDSAVE;
         }
     if (!(pdlf->rgbAction & DLG_NOOPEN) &&
         !(OpenFile( (PSZ)pdlf->szFileName
                   , pdlf->phFile
                   , (PSZ)pdlf->szOpenFile
                   , OF_WRITE)))
         {
         DlgAlertBox(hwnd, IDS_EOF, pdlf,
                     MB_OK | MB_ICONEXCLAMATION);
         return;
         }
     WinDismissDlg(hwnd, usTdf);
     }



 /****************************************************************************
 * This function initializes the SaveAs dialog box.  It puts the current
 * directory string in the ID_PATH field and initializes the edit box
 * with the proposed filename.  It is the simple relative file name if current
 * dir == pdlf->szOpenFile.  Otherwise, it is the fully qualified name.
 \****************************************************************************

 VOID PASCAL DlgInitSaveAs (hwnd, lpParm)
 HWND hwnd;
 ULONG lpParm;
     {
     PDLF pdlf;
     CHAR sz[MAX_FNAME_LEN];
     PSZ  lpszFile, lpszFN, lpszCD;

     /* set pdlf local to window */
     pdlf = (PDLF) lpParm;
     WinSetWindowULong(hwnd, 0, lpParm);

     /* set edit box text size limit */
     WinSendDlgItemMsg(hwnd, ID_EDIT, EM_SETTEXTLIMIT, (MPARAM)CBROOTNAMEMAX,

     /* set title window */
     if (pdlf->pszTitle != (PSZ)NULL)
         WinSetWindowText(WinWindowFromID(hwnd, FID_TITLEBAR), pdlf->pszTitle)

     /* set szLastWild to search spec */
     if (pdlf->pszExt != (PSZ)NULL)
         lstrcpy( (PSZ)pdlf->szLastWild, (PSZ)pdlf->pszExt+1 );

     /* get current directory */
     DosSearchPath(0, (PSZ)szDot, (PSZ)szStarStar, (PSZ)sz, MAX_FNAME_LEN);
     lpszFile = FileInPath((PSZ)sz);
     if (lpszFile > (PSZ)sz + 3)
         lpszFile--;
     *lpszFile = '\0';

     /* compare path part name to previously opened file name and
        make the file name relative if they are the same */
     lpszFN = (PSZ)pdlf->szOpenFile;
     lpszCD = (PSZ)sz;
     lpszFile = FileInPath((PSZ)pdlf->szOpenFile);
     while (lpszFN < lpszFile && *lpszFN == *lpszCD)
         {
         lpszFN = NextChar(lpszFN);
         lpszCD = NextChar(lpszCD);
         }
     if (*lpszCD)
         lpszFN = (PSZ)pdlf->szOpenFile;
     else if (*lpszFN == '\\')
         lpszFN = NextChar(lpszFN);
     Upper( lpszFN);
     WinSetWindowText(WinWindowFromID(hwnd, ID_EDIT), lpszFN);
     lstrcpy( (PSZ)pdlf->szLastFile, lpszFN);

     /* set current path field */
     WinSetWindowText(WinWindowFromID(hwnd, ID_PATH),
                      DlgFitPathToBox(hwnd, ID_PATH, (PSZ)sz));
     }




 /****************************************************************************
 * This function returns the filename part of the given path string.
 \****************************************************************************

 PSZ  EXPENTRY FileInPath(lpsz)
 PSZ  lpsz;
     {
     PSZ  lpch;

     /* strip path/drive specification from name if there is one */
     lpch = lpsz + lstrlen(lpsz);
     while (lpch > lpsz)
         {
         lpch = PrevChar(lpsz, lpch);
         if (*lpch == '\\' || *lpch == ':')
             {
             lpch = NextChar(lpch);
             break;
             }
         }
     return(lpch);
     }




 /****************************************************************************
 * This function adds the extension to a file name if it is missing.
 \****************************************************************************

 VOID EXPENTRY AddExt(lpsz, lpszExt)
 PSZ  lpsz;
 PSZ  lpszExt;
     {
     PSZ  lpch;

     lpch = lpsz + lstrlen(lpsz);
     while (*lpch != '.' && *lpch != '\\' && *lpch != ':' && lpch > lpsz)
         lpch = PrevChar(lpsz, lpch);

     if (*lpch != '.')
         lstrcat(lpsz, (PSZ)(lpszExt+2));
     }




 /****************************************************************************
 * This function adds the "appropriate" extension to a filename, partial
 * filename, search spec or partial search spec.
 \****************************************************************************

 VOID PASCAL DlgAddSearchExt(pdlf, lpszEdit)
 PDLF pdlf;
 PSZ  lpszEdit;
     {
     PSZ  lpchLast;

     lpchLast = PrevChar(lpszEdit, lpszEdit + lstrlen(lpszEdit));
     if (*lpchLast == '\\' || *lpchLast == ':')
         lstrcat(lpszEdit, pdlf->pszExt + 1);
     else
         lstrcat(lpszEdit, pdlf->pszExt);
     }




 /****************************************************************************
 * This function returns TRUE if lpsz contains a wildcard character '*' or '?'
 \****************************************************************************

 BOOL PASCAL DlgSearchSpec(lpsz)
 PSZ  lpsz;
    {
     for (; *lpsz; lpsz = NextChar(lpsz))
         if (*lpsz == '*' || *lpsz == '?')
             return TRUE;
     return FALSE;
    }




 /****************************************************************************
 * This function displays an error or warning msg.
 \****************************************************************************

 int PASCAL DlgAlertBox(hwnd, ids, pdlf, wStyle)
 HWND hwnd;
 int ids;
 PDLF pdlf;
 USHORT wStyle;
     {
     /* note:  5th param is idHelp */
     return (AlertBox(hwnd, ids, (PSZ)pdlf->szFileName, pdlf->pszAppName,
                          NULL, wStyle | MB_APPLMODAL));
     }


 FILE1.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\FILE1.C

 /*
     FILE1.C -- Dialog Directory Listbox and Open File functions
     Created by Microsoft Corporation, 1989
 */

 #define  INCL_DOS
 #include "tool.h"

 /****************************************************************************
 * This function builds a directory list in a list box.
 \****************************************************************************

 int FAR PASCAL DlgDirList(hwnd, lpszPath, idDirList, idFileList,
                           idStaticPath, attr)
 HWND hwnd;
 PSZ  lpszPath;
 int idDirList;
 int idFileList;
 int idStaticPath;
 USHORT attr;
     {
     CHAR szPath[MAX_FNAME_LEN];
     CHAR chTmp;
     PSZ  lpszFile, lpszNull;
     HPOINTER hPointer;

     /* ensure path is legal and expand to full search path */
     if (!DlgParseFile(lpszPath, (PSZ)szPath, FALSE, TRUE))
         return FALSE;

     /* set pointer to hours glass */
     hPointer = WinQueryPointer(HWND_DESKTOP);
     WinSetPointer(HWND_DESKTOP, WinQuerySysPointer(HWND_DESKTOP, SPTR_WAIT, F

     /* set current drive */
     DosSelectDisk(*szPath - 'A' + 1);

     /* temporarily put zero after directory spec and set current directory */
     lpszFile = FileInPath((PSZ)szPath);
     lpszNull = lpszFile;
     if (lpszNull > (PSZ)szPath + 3)
         lpszNull--;
     chTmp = *lpszNull;
     *lpszNull = '\0';
     DosChDir((PSZ)szPath, 0l);
     if (idStaticPath)
         WinSetWindowText(WinWindowFromID(hwnd, idStaticPath),
                          DlgFitPathToBox(hwnd, idStaticPath, (PSZ)szPath));
     *lpszNull = chTmp;

     /* fill list box with file names */
     if (idDirList && idFileList)
         {
         /* fill them up with new entries */
         DlgFillListBoxes(hwnd, idDirList, idFileList, attr, lpszFile);
         }

     /* reset pointer to previous figure */
     WinSetPointer(HWND_DESKTOP, hPointer);

     return TRUE;
     }




 /****************************************************************************
 * This function gets the file name selected by the user.
 \****************************************************************************

 int FAR PASCAL DlgDirSelect(hwnd, lpszIn, idListBox)
 HWND hwnd;
 PSZ  lpszIn;
 int idListBox;
     {
     CHAR sz[MAX_FNAME_LEN];
     int item;
     PSZ  lpsz, lpszFile;
     BOOL fDir;

     /* get currently selected list entry */
     item = (int) SHORT1FROMMR(WinSendDlgItemMsg(hwnd, idListBox, LM_QUERYSELE
     if (item == LIT_NONE)
         return FALSE;
     WinSendDlgItemMsg(hwnd, idListBox, LM_QUERYITEMTEXT,
                       MPFROM2SHORT(item, MAX_FNAME_LEN), (MPARAM)(PSZ)sz);
     lpszFile = sz;

     /* extract name */
     if (fDir = (*sz == '['))
         {
         lpszFile = NextChar(lpszFile);
         if (*lpszFile == '-')
             {
             /* drive selection */
             lpszFile = NextChar(lpszFile);
             *(lpszFile+1) = ':';
             *(lpszFile+2) = '\0';
             }
         else
             {
             /* directory selection */
             lpsz = lpszFile;
             while (*lpsz != ']')
                 lpsz = NextChar(lpsz);
             *lpsz = '\\';
             }
         }

     lstrcpy(lpszIn, lpszFile);
     return (fDir);
     }




 /****************************************************************************
 * This function tries to open pdlf=>szFileName.  If the file does not
 * exist, the function asks to create it.
 *
 *  Returns:
 *    TDF_NOOPEN  - Illegal Filename or user didn't want to create
 *    TDF_OLDOPEN - Existing file
 *    TDF_NEWOPEN - File was created
 \****************************************************************************

 USHORT PASCAL DlgOpenFile(pdlf, hwnd)
 PDLF pdlf;
 HWND hwnd;
     {
     /* check for legal dos name */
     if (!DlgParseFile((PSZ)pdlf->szFileName, (PSZ)pdlf->szOpenFile,
                       FALSE, FALSE))
         {
         DlgAlertBox(hwnd, IDS_IFN, pdlf, MB_OK | MB_ICONEXCLAMATION);
         return (TDF_NOOPEN);
         }
     /* see if file already exists */
     if (DlgParseFile((PSZ)pdlf->szFileName, (PSZ)pdlf->szOpenFile,
                       TRUE, FALSE))
         {
         if (OpenFile((PSZ)pdlf->szFileName, pdlf->phFile,
                         (PSZ)pdlf->szOpenFile, OF_READ))
             return (TDF_OLDOPEN);
         else
             {
             DlgAlertBox(hwnd, IDS_EOF, pdlf, MB_OK | MB_ICONEXCLAMATION);
             return (TDF_NOOPEN);
             }
         }
     /* file doesn't exist: create new one? */
     else if (DlgAlertBox(hwnd, IDS_FNF, pdlf, MB_YESNO | MB_ICONQUESTION) ==
         {
         if (OpenFile((PSZ)pdlf->szFileName, pdlf->phFile,
                         (PSZ)pdlf->szOpenFile, OF_CREATE))
             return (TDF_NEWOPEN);
         else
             DlgAlertBox(hwnd, IDS_ECF, pdlf, MB_OK | MB_ICONEXCLAMATION);
         }
     return(TDF_NOOPEN);
     }




 /****************************************************************************
 * This function returns the OS/2 file handle if operation is successful,
 * 0 otherwise.
 *
 * Effects:  Depend on wMode:
 *     OF_READ:      open file for reading only
 *     OF_WRITE:     open file for writing only
 *     OF_READWRITE: open file for reading and writing
 *     OF_CREATE:    create the file if it does not exist
 *     OF_REOPEN:    open file using info in the reopen buffer
 *     OF_EXIST:     test file existence
 *     OF_PARSE:     fill reopen buffer, with no other action
 \****************************************************************************


 BOOL EXPENTRY OpenFile(lpszFile, lpHandle, lpszOpenFile, wMode)
 PSZ  lpszFile;
 PHFILE lpHandle;
 PSZ  lpszOpenFile;
 USHORT wMode;
     {
     HFILE hFile = NULL;
     USHORT wAction = NULL;
     USHORT wAttrs = NULL;
     USHORT wOpenFlag, wOpenMode;
     CHAR sz[MAX_FNAME_LEN];

     /* if reopen specified, use earlier pathname */
     if (wMode & OF_REOPEN)
         {
         lstrcpy((PSZ)sz, lpszOpenFile);
         lpszFile = (PSZ)sz;
         }

     /* parse file */
     if (!DlgParseFile(lpszFile, lpszOpenFile, wMode&OF_EXIST, FALSE))
         {
         *lpszOpenFile = '\0';
         return FALSE;
         }
     /* return if implementing boolean test OF_PARSE or OF_EXIST */
     if (wMode & (OF_PARSE | OF_EXIST))
         {
         return TRUE;
         }

     if (wMode & OF_READ)
         {
         wOpenFlag = 0x01;               /* fail if it doesn't exist */
         wOpenMode = 0x20;               /* read only */
         }
     else if (wMode & OF_WRITE)
         {
         wOpenFlag = 0x11;               /* create if necessary */
         wOpenMode = 0x11;               /* write only */
         }
     else if (wMode & OF_READWRITE)
         {
         wOpenFlag = 0x11;               /* create if necessary */
         wOpenMode = 0x12;               /* read-write */
         }
     else if (wMode & OF_CREATE)
         {
         /* create and close file */
         wOpenFlag = 0x10;               /* fail if exists */
         wOpenMode = 0x10;               /* read only */
         }
     else
         {
         return FALSE;
         }

     if (DosOpen(lpszFile, (PHFILE)&hFile, &wAction,
                 (ULONG) 0, 0, wOpenFlag, wOpenMode, (ULONG) 0))
         return FALSE;

     if (wMode & OF_CREATE)
         {
         if (DosClose(hFile))
             return FALSE;
         }
     else
         *lpHandle = hFile;

     return TRUE;
     }




 /****************************************************************************
 * This function puts a path string into a static box.
 \****************************************************************************

 PSZ  PASCAL DlgFitPathToBox(hwnd, idStatic, lpch)
 HWND hwnd;
 int idStatic;
 PSZ  lpch;
     {
     WRECT rc;
     int cxField;
     char chDrive;
     HPS hps;

     /* get length of static field */
     WinQueryWindowRect(WinWindowFromID(hwnd, idStatic), (PRECTL)&rc);
     cxField = rc.xRight - rc.xLeft;

     hps = WinGetPS(hwnd);
     if (cxField < (int) LOUSHORT(GetTextExtent(hps, lpch,
                                            lstrlen(lpch))))
         {
         chDrive = *lpch;
         /* chop characters off front of string until text is short enough */
         do
             do
                 lpch = NextChar(lpch);
             while (*(lpch+6) != '\\' &&
                    *(lpch+6) != '\0');
         while (cxField < (int) LOUSHORT(GetTextExtent(hps, lpch,
                                                  lstrlen(lpch))));
         /* insert header */
         *lpch++ = chDrive;
         *lpch++ = ':';
         *lpch++ = '\\';
         *lpch++ = '.';
         *lpch++ = '.';
         *lpch++ = '.';
         lpch -= 6;
         }
     WinReleasePS(hps);
     return (lpch);
     }




 /****************************************************************************
 * This function fills a list box with appropriate file names from the
 * current directory.
 \****************************************************************************

 int PASCAL DlgFillListBoxes(hwnd, idDirList, idFileList, attr, lpszFileSpec)
 HWND   hwnd;
 int    idDirList;
 int    idFileList;
 USHORT attr;
 PSZ    lpszFileSpec;
     {
     USHORT usFiles, usDrive;
     int i;
     PSZ  lpsz;
     HDIR hDir;
     ULONG lrgfDrives;
     FILEFINDBUF ffb;
     CHAR sz[20];
     int cDrives;
     int result = -1;
     HWND hwndFiles, hwndDirs;

     /* get listbox window handles */
     hwndFiles = WinWindowFromID(hwnd, idFileList);
     hwndDirs = WinWindowFromID(hwnd, idDirList);

     /* disable updates to listboxes */
     WinEnableWindowUpdate(hwndFiles, FALSE);
     WinEnableWindowUpdate(hwndDirs, FALSE);

     /* empty list boxes of any old entries */
     WinSendMsg(hwndFiles, LM_DELETEALL, 0L, 0L);
     WinSendMsg(hwndDirs, LM_DELETEALL, 0L, 0L);

     /* put files that lMatch search spec in file listbox */
     usFiles = 1;
     hDir = 0xFFFF;
     if (!DosFindFirst(lpszFileSpec, (PHDIR)&hDir,
         attr&~(BITATTRDIR|BITATTRDRIVE), (PFILEFINDBUF)&ffb, sizeof(FILEFINDB
         &usFiles, 0L))
         {
         do
             {
             /* add string to list box */
             result = (int) SHORT1FROMMR(WinSendMsg(hwndFiles, LM_INSERTITEM,
                                       MPFROM2SHORT(LIT_SORTASCENDING, 0),
                                       (MPARAM)(PSZ)&(ffb.achName[0])));
             usFiles = 1;
             }
         while (result >= 0 && !DosFindNext(hDir,
                                            (PFILEFINDBUF)&ffb,
                                            sizeof(FILEFINDBUF),
                                            &usFiles));
         DosFindClose(hDir);
         }

     if (result != LIT_MEMERROR && (attr   & BITATTRDIR))
         {
         /* get directories */
         usFiles = 1;
         hDir = 0xFFFF;
         if (!DosFindFirst((PSZ)szStarStar, (PHDIR)&hDir, BITATTRDIR,
             (PFILEFINDBUF)&ffb, sizeof(FILEFINDBUF), &usFiles, 0l))
             {
             do
                 {
                 /* extract file name */
                 if (ffb.attrFile & BITATTRDIR)
                     {
                     /* put brackets around directory */
                     lpsz = (PSZ)&(ffb.achName[0]);
                     if (*lpsz == '.' && *(lpsz+1) == '\0')
                         /* forget about current directory name '.' */
                         continue;
                     sz[0] = '[';
                     lstrcpy((PSZ)&sz[1], lpsz);
                     sz[ffb.cchName    + 1] = ']';
                     sz[ffb.cchName    + 2] = '\0';
                     lpsz = (PSZ)sz;
                     /* add string to list box */
                     result = (int) SHORT1FROMMR(WinSendMsg(hwndDirs, LM_INSER
                                               MPFROM2SHORT(LIT_SORTASCENDING,
                                               (MPARAM)lpsz));
                     }
                 usFiles = 1;
                 }
             while (result >= -1 && !DosFindNext(hDir,
                                                (PFILEFINDBUF)&ffb,
                                                sizeof(FILEFINDBUF),
                                                &usFiles));
             DosFindClose(hDir);
             }
         }

     if (result != LIT_MEMERROR && (attr   & BITATTRDRIVE))
         /* get drives */
         {
         sz[0] = '[';
         sz[1] = sz[3] = '-';
         sz[4] = ']';
         sz[5] = '\0';

         DosQCurDisk(&usDrive, (unsigned long far *)&lrgfDrives);
         cDrives = 0;
         for (i=0; 'A' + i <= 'Z'; i++)
             {
             if (lrgfDrives & 1L)
                 {
                 sz[2] = (char)('A' + i);
                 /* add drive to list */
                 result = (int) SHORT1FROMMR(WinSendMsg(hwndDirs, LM_INSERTITE
                                           MPFROM2SHORT(LIT_END, 0),
                                           (MPARAM)(PSZ)sz));
                 cDrives++;
                 }
             lrgfDrives >>= 1;
             }
         }

     /* enable and show updated listboxes */
     WinShowWindow(hwndFiles, TRUE);
     WinShowWindow(hwndDirs, TRUE);

     return result;
     }




 /****************************************************************************
 * This function parses a string into an fully expanded file name or search
 * path.  If fExist is true then the file must exist or the search path must
 * correspond to at least one existing file.  In all cases, the corresponding
 * directory must exist.  The string must be a file name, ie. no wildcards,
 * unless fWildOkay is true.  Returns TRUE if string is parsed correctly.
 \****************************************************************************

 BOOL PASCAL DlgParseFile(lpszIn, lpszExp, fExist, fWildOkay)
 PSZ  lpszIn, lpszExp;
 BOOL fExist, fWildOkay;
     {
     CHAR szPath[MAX_FNAME_LEN];
     PSZ  lpszFile, lpsz;

     /* go past any path spec to first char in file name */
     lpszFile = FileInPath(lpszIn);

     /* check validity of file name */
     if (!fExist && !DlgValidName(lpszFile, fWildOkay))
         {
         *lpszExp = '\0';
         return FALSE;
         }

     /* copy path part to path string */
     lpsz = (PSZ)szPath;
     if (lpszIn == lpszFile)
         *lpsz++ = '.';
     else
         {
         while (lpszIn < lpszFile && lpsz < ((PSZ)szPath + MAX_FNAME_LEN - 1))
             *lpsz++ = *lpszIn++;
         }
     *lpsz = '\0';

     /* let DOS do the dirty work */
     if (fExist)
         {
         /* test existence of file while parsing path */
         if (DosSearchPath(0, (PSZ)szPath, lpszFile, lpszExp, MAX_FNAME_LEN))
             {
             *lpszExp = '\0';
             return FALSE;
             }
         }
     else
         {
         /* use dummy wild card while parsing path */
         if (DosSearchPath(0, (PSZ)szPath, (PSZ)szStarStar, lpszExp, MAX_FNAME
             {
             *lpszExp = '\0';
             return FALSE;
             }
         /* replace wild card part with true file name converted to CAPS */
         lpsz = lpszExp;
         while (*lpsz != '?' && *lpsz != '*' && *lpsz != '\0')
             lpsz++;
         while (*lpszFile != '\0')
             {
             *lpsz++ = *lpszFile++;
             }
         *lpsz = '\0';
         Upper(lpszExp);
         }
     return TRUE;
     }




 /****************************************************************************
 * This function determines if a string forms a legal file name or search
 * path.  Wildcard characters are acceptable if fWildOkay is true.  Returns
 * TRUE if string is legal.
 \****************************************************************************

 BOOL PASCAL DlgValidName(lpszName, fWildOkay)
 PSZ  lpszName;
 BOOL fWildOkay;
     {
     int cLen;
     PSZ  lpsz;

     /* check file name */
     if (*lpszName == '\0')
         return FALSE;
     cLen = 0;
     lpsz = lpszName;
     while (*lpsz != '\0' && *lpsz != '.')
         {
         if (++cLen > 8 || *lpsz < 0x20)
             return FALSE;
         switch (*lpsz++)
             {
             case '*':
             case '?':
                 if (fWildOkay)
                     break;
             case '\"':
             case '\\':
             case '/':
             case '[':
             case ']':
             case ':':
             case '|':
             case '<':
             case '>':
             case '+':
             case '=':
             case ';':
             case ',':
                 return FALSE;
             }
         }

     /* check extension */
     if (*lpsz++ == '\0')
         return TRUE;
     cLen = 0;
     while (*lpsz != '\0')
         {
         if (++cLen > 3 || *lpsz < 0x20)
             return FALSE;
         switch (*lpsz++)
             {
             case '*':
             case '?':
                 if (fWildOkay)
                     break;
             case '.':
             case '\"':
             case '\\':
             case '/':
             case '[':
             case ']':
             case ':':
             case '|':
             case '<':
             case '>':
             case '+':
             case '=':
             case ';':
             case ',':
                 return FALSE;
             }
         }
     return TRUE;
     }


 GPI.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\GPI.C

 /***************************************************************************\
 * GPI.C -- GPI Helper routines
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/


 #define INCL_GPI
 #include "tool.h"

 /***************************************************************************\
 * GetTextExtent helper function
 \***************************************************************************/

 ULONG EXPENTRY GetTextExtent(HPS hps, PCH lpstr, int cch) {
     POINTL rgptl[TXTBOX_COUNT];

     if (cch) {
         GpiQueryTextBox(hps, (LONG)cch, lpstr, 5L, rgptl);
         return(MAKEULONG((SHORT)(rgptl[TXTBOX_CONCAT].x - rgptl[TXTBOX_BOTTOM
                      (SHORT)(rgptl[TXTBOX_TOPLEFT].y - rgptl[TXTBOX_BOTTOMLEF
     } else
         return(0L);
     }


 HANOI.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\HANOI\HANOI.C

 /*************************************************************

  This program implements a tower of hanoi program.  This
  sample app was written to demonstrate the use of a multi-
  threaded program.  The main thread handles the PM interface,
  the second thread is started to do the recursive execution
  of the hanoi algorithm.

  This program was written by Jeff Johnson, 7/89.

  Procedures in this file:
    main()             Sets up the PM environment and heap and
                       calls the main window procedure ClientWndProc
    ClientWndProc()    Handles the main window messages
    CalcThread()       Sets up and terminates the secondary thread
    DrawDisk()         Draws or erases a disk on one of the poles
    MoveDisk()         Moves a disk from one pole to another
    Hanoi()            Recursive alogrithm for tower of hanoi prog
    EnableMenuItem()   Activates/deactivates a menu choice
    EntryFldDlgProc()  Handles the set number of disks dialog box
    SetupTowers()      Sets up the global tower data

 **************************************************************/

 #include "hanoi.h"


 /********************* GLOBALS *******************************/

 CHAR szClientClass[] = "Hanoi";

 /* Note that this use of global data precludes multiple windows
    of hanoi running at the same time.  Thus, from an object-
    oriented perspective, this is less than desireable and the
    data should be passed into the window, rather than used
    explicitly. */

 BYTE abTowers[3][MAXDISKCNT];     /* Used to hold disk numbers on each post *
 BYTE abTowersNdx[3];              /* Current number of disks on each post   *
 BYTE cTowerSize = DEFAULTSIZE;   /* Holds the total number of disks        */
 USHORT ausPolePos[3]= { POSTOFFSET, /* Holds offset drawing information     *
                        POSTOFFSET + POSTSPACE,
                        POSTOFFSET + 2*POSTSPACE };
 ULONG  ulIterations;

 /*************************************************************/


 /*
  * Function name: main()
  *
  * Parameters:  argc, argv.  If the user places a number (1 thru MAXDISKCNT)
  *              on the command line, that number will become the default
  *              number of disks on the stack. Otherwise there will be
  *              DEFUALTSIZE disks initially.
  *
  * Returns: Always returns 0
  *
  * Purpose: Parses the command line, sets up the PM environment, prepares
  *          the Tower arrays, calls the main window proc, handles the
  *          window's messages then cleans up and exits.
  *
  * Usage/Warnings:
  *
  * Calls: ClientWndProc() (thru PM)
  */

 int main(int argc, char *argv[])
 {
    HAB          hab;
    HMQ          hmq;
    HWND         hwndFrame, hwndClient;
    QMSG         qmsg;

    ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU | FCF_MINBUTTON |
                         FCF_SHELLPOSITION | FCF_MENU    | FCF_TASKLIST  |
                         FCF_ICON          | FCF_BORDER  | FCF_ACCELTABLE;

    SHORT sHold;

    if(argc > 1)  /* If command line arg, use as the initial number of disks *
    {
       sHold = atoi(argv[1]);
       if(sHold>0 && sHold<=MAXDISKCNT)
          cTowerSize = (BYTE) sHold;
    }
    SetupTowers();

    /* These PM calls should be error checked */
    hab = WinInitialize(0);
    hmq = WinCreateMsgQueue(hab, 0);

    if(!WinRegisterClass(hab, szClientClass,ClientWndProc,0L,0))
    {
       WinAlarm(HWND_DESKTOP, WA_ERROR);        /* Register failed */
       DosExit(EXIT_PROCESS,1);
    }

    hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE,
                                   &flFrameFlags, szClientClass, NULL,
                                   0L, (HMODULE) NULL, ID_MAINMENU, &hwndClien
    if(!hwndFrame)
    {
       WinAlarm(HWND_DESKTOP, WA_ERROR); /* Window create failed */
       DosExit(EXIT_PROCESS,1);
    }

    while(WinGetMsg(hab,&qmsg,NULL,0,0))        /* Message loop */
       WinDispatchMsg(hab,&qmsg);

    WinDestroyWindow(hwndFrame);                /* Clean up     */
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);
    return 0;
 }


 /*
  * Function name: ClientWndProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Window Proc params.
  *              No user data is expected in the WM_CREATE.
  *
  * Returns:
  *
  * Purpose: Handles all the messages associated with the main window
  *          and calls the appropriate handling procedures.
  *
  * Usage/Warnings: Called only by main().  Note that when WM_PAINT executes,
  *                 the secondary thread may change data during the update
  *                 which may cause a problem.  However, this is NOT a write
  *                 conflict, as only 1 thread does the writing.
  *
  * Calls:  DrawDisk(), CalcThread() (thru Thread), EntryFldDlgProc() (thru PM
  */

 MRESULT EXPENTRY ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    HPS    hps;                         /* Handle for painting           */
    RECTL  rcl;                         /* Rectangle struct for painting */
    POINTL ptl;                         /* Point struct for painting     */
    BYTE   cPole,bCnt,bHeight,cnt;      /* Utility variables             */
    CHAR   szMsg[MSGBUFSIZE];           /* sprintf buffer                */
    static CALCPARAM cp;                /* Struct used to notify thread  */
    static TID    tidCalc;              /* Secondary thread ID           */
    static VOID   *pThreadStack;        /* Pointer to secondary stack    */

    switch(msg)
    {
       case WM_PAINT:
          hps = WinBeginPaint(hwnd,NULL,NULL);   /* Get paint handle     */
          WinQueryWindowRect(hwnd,&rcl);

          DrawRect(rcl.xLeft,rcl.yBottom,        /* White out the screen */
                   rcl.xRight,rcl.yTop,CLR_WHITE);

          /* Draw the base */
          DrawRect(BASEXOFFSET,           BASEYOFFSET,
                   BASEXOFFSET+BASELEN-1, BASEYOFFSET+BASETHICK-1,
                   CLR_DARKGREEN);

          /* Draw the 3 posts */
          bHeight = (BYTE) (cTowerSize*DISKSPACE + POSTEXTRAHT);
          for(cnt=0;cnt<3;cnt++)
          {
             DrawRect(ausPolePos[cnt]-POSTHALF,          BASEYOFFSET,
                      ausPolePos[cnt]-POSTHALF+POSTWIDTH,bHeight,
                      CLR_DARKGREEN);
          }

          /* Place the appropriate disks on each pole */
          for(cPole=0;cPole<3;cPole++)
          {
             for(bCnt=0;bCnt<abTowersNdx[cPole];bCnt++)
             {
                DrawDisk(hps,cPole,bCnt,fDRAW);
             }
          }

          WinEndPaint(hps);
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case IDM_START:
                /* Try to get stack space */
                if((pThreadStack = malloc(STACKSIZE)) == NULL)
                {
                   WinAlarm(HWND_DESKTOP, WA_ERROR);  /* Couldn't get memory *
                   return 0L;
                }
                cp.hwnd = hwnd;           /* Set the static struct  */
                cp.fContinueCalc = TRUE;
                ulIterations = 0;         /* Zero iteration counter */

                /* Try to start the thread */
                if((tidCalc = _beginthread(CalcThread,pThreadStack,
                                           STACKSIZE, &cp))     == -1)
                {
                   free(pThreadStack);    /* Thread wouldn't start  */
                   WinAlarm(HWND_DESKTOP, WA_ERROR);
                   return 0L;
                }
                /* Disallow menu items that could change data while the second
                   thread is running */
                EnableMenuItem(hwnd,IDM_START,FALSE); /* Disable Start & set *
                EnableMenuItem(hwnd,IDM_SET,FALSE);
                EnableMenuItem(hwnd,IDM_STOP,TRUE);   /* Enable Stop item    *
                return 0L;

             case IDM_STOP:
                cp.fContinueCalc = FALSE;  /* Notify thread to quit          *
                return 0L;

             case IDM_SET:
                if(WinDlgBox(HWND_DESKTOP, hwnd, /* Pop up the query/set box *
                          EntryFldDlgProc,(HMODULE) NULL,ID_SETCOUNT,NULL))
                {
                   SetupTowers();                          /* Reset towers   *
                   WinInvalidateRect(hwnd,NULL,FALSE);     /* Force a redraw *
                }
                return 0L;

              default:
                 return WinDefWindowProc(hwnd, msg, mp1, mp2);
          }

       case UM_CALC_DONE:
          EnableMenuItem(hwnd,IDM_START,TRUE);  /* Reenable Start & set      *
          EnableMenuItem(hwnd,IDM_SET,TRUE);
          EnableMenuItem(hwnd,IDM_STOP,FALSE);  /* Disable stop              *
          free(pThreadStack);                   /* Free thread's stack space *

          sprintf(szMsg,"%lu disks were moved.",ulIterations);  /* Print msg *
          WinMessageBox(HWND_DESKTOP, hwnd, szMsg, "Done!", 0, MB_OK);

          SetupTowers();                        /* Reset towers              *
          WinInvalidateRect(hwnd,NULL,FALSE);   /* Force a screen redraw     *
          return 0L;

        default:
           return WinDefWindowProc(hwnd, msg, mp1, mp2);
    }
 }


 /*
  * Function name: CalcThread()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle and the
  *              continue flag which is initially set to TRUE.
  *
  * Returns: VOID
  *
  * Purpose: Calls the recursive Hanoi with initial setting of 0,2,1 meaning
  *          from pole 0, to pole 2, using pole 1 as a temporary.  Hanoi
  *          returns when finished, or the user says stop.  This proc then
  *          sets a critical section so the posted message won't be handled
  *          until the thread is terminated.
  *
  * Usage/Warnings: No DosExitCritSec() is called since _endthread() supposedl
  *                 clears the critical section when the thread is
  *                 terminated.
  *
  * Calls:  Hanoi()
  */

 VOID _cdecl FAR CalcThread(PCALCPARAM pcp)
 {
    HAB hab;

    hab = WinInitialize(0);    /* Called to increase Ring 2 stack size */
    Hanoi(pcp,cTowerSize,0,2,1);      /* Execute the recursive routine */
    WinTerminate(hab);

    DosEnterCritSec(); /* Set Crit so the UM_CALC_DONE isn't processed */
                       /* until this thread has completely terminated  */
    WinPostMsg(pcp->hwnd,UM_CALC_DONE,NULL,NULL);         /* Post done */

    _endthread();                                  /* Terminate thread */
 }


 /*
  * Function name: DrawDisk()
  *
  * Parameters:  hps is a handle to the main PS space.
  *              cPole is the pole (0-2) to draw the disk on.
  *              bLevel is the number of spaces from the bottom to draw disk.
  *              fDraw if =0, erase disk, if =1 draw disk.
  *
  * Returns: VOID
  *
  * Purpose: This routine takes a PS handle, the hanoi spindle and disk level
  *          and draws an appropriately sized disk.
  *
  * Usage/Warnings: Does not grab exclusive access to the screen before
  *                 drawing which may cause a problem.
  *
  * Calls:
  */

 VOID DrawDisk(HPS hps,BYTE cPole, BYTE bLevel,BYTE fDraw)
 {
    USHORT usXstart,usYstart,usWidth;
    POINTL ptl;

    usYstart = BOTDISKYPOS + DISKSPACE*bLevel;  /* Calculate Bottom of disk

    usWidth  = (MAXDISKWIDTH-MINDISKWIDTH)*abTowers[cPole][bLevel]/cTowerSize
               + MINDISKWIDTH;                  /* Calculate the disk's width

    usXstart = ausPolePos[cPole] - usWidth/2;   /* Center disk on pole

    if(fDraw == fDRAW)  /* If we are to draw the disk */
    {
       DrawRect(usXstart,usYstart,usXstart+usWidth,
                usYstart+DISKTHICK-1,CLR_RED);
    }
    else         /* We are to erase the disk, then redraw the pole */
    {
       DrawRect(usXstart,usYstart,usXstart+usWidth,
                usYstart+DISKTHICK-1,CLR_WHITE);
       DrawRect(ausPolePos[cPole]-POSTHALF,usYstart,
                ausPolePos[cPole]-POSTHALF+POSTWIDTH,usYstart+DISKTHICK-1,
                CLR_DARKGREEN);
    }
 }


 /*
  * Function name: MoveDisk()
  *
  * Parameters:  hps is a handle to the main PS space.
  *              bFrom is the spindle to take the top disk from.
  *              bTo is the spindle to place the disk on.
  *
  * Returns: VOID
  *
  * Purpose: This routine moves the top disk from the bFrom spindle to the top
  *          of the bTo spindle.
  *
  * Usage/Warnings: Does error checking for trying to move a disk from a
  *                 spindle that does not have any disks on it.
  *
  * Calls:  MoveDisk()
  */

 VOID MoveDisk(HPS hps,BYTE bFrom,BYTE bTo)
 {
    CHAR bTOSndx;  /* Top of stack index  */
    BYTE bDiskNum; /* Disk number to move */

    bTOSndx = (CHAR) (abTowersNdx[bFrom]-1);
    if(bTOSndx < 0)
       return;

    DrawDisk(hps,bFrom,bTOSndx,fERASE);   /* Remove disk off from stack     */

    bDiskNum = abTowers[bFrom][bTOSndx];  /* Get move disk number           */
    abTowersNdx[bFrom]--;                 /* Decrease count on from spindle */

    bTOSndx = abTowersNdx[bTo]++;         /* Offset to place the disk at    */
    abTowers[bTo][bTOSndx] = bDiskNum;    /* Place on stack in memory       */

    DrawDisk(hps,bTo,bTOSndx,fDRAW);      /* Draw disk on the to stack      */
 }


 /*
  * Function name: Hanoi()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle and the
  *                  continue flag which is initially set to TRUE.
  *              bHeight is the number of disks in the from stack to move.
  *              bFrom is the from spindle number, 0-2.
  *              bTo is the to spindle number.
  *              bTemp is the temporary spindle number.
  *
  * Returns: VOID
  *
  * Purpose: This routine implements a recursive hanoi program that works as
  *          follows:  By recursion, move all the disks, except for the
  *          bottom disk to the temporary stack.  Then move the bottom
  *          disk to the target spindle.  Now recursively move the stack
  *          on the temporary spindle to the target spindle.  The limiting
  *          case is when Hanoi() is called with a bHeight of 0 in which
  *          case the depth recursion is terminated.
  *
  * Usage/Warnings: This routine checks the ->fContinueCalc flag, which is set
  *                 by the main thread when the user selects stop, to see if
  *                 the user wishes to abort the algorithm.  If so, it backs
  *                 out and exits.
  *
  * Calls:  MoveDisk()
  */

 VOID Hanoi(PCALCPARAM pcp, BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp)
 {
    HPS hps;

    if(bHeight<=0 || !pcp->fContinueCalc)  /* Exit up if no more disks or */
       return;                             /* the user said Stop          */

    Hanoi(pcp,(BYTE)(bHeight-1),bFrom,bTemp,bTo);  /* Move all but bottom disk

    if(!pcp->fContinueCalc)                /* If user said to stop        */
       return;

    /* Display bFrom -> bTo */
    hps = WinGetPS(pcp->hwnd);
    MoveDisk(hps,bFrom,bTo);               /* Move the bottom disk        */
    WinReleasePS(hps);
    ulIterations++;

    Hanoi(pcp,(BYTE)(bHeight-1),bTemp,bTo,bFrom);  /* Move disks over
 }


 /*
  * Function name: EnableMenuItem()
  *
  * Parameters:  hwnd is a handle of the current window.
  *              sMenuItem is the ID of the item to Enable/Disable.
  *              fEnable will enable item if TRUE, otherwise disables it.
  *
  * Returns: VOID
  *
  * Purpose: This routine handles enabling/disabling of menu items.  This
  *          is done by getting Parent and Menu hwnd handles then sending
  *          the appropriate message.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 VOID EnableMenuItem(HWND hwnd, SHORT sMenuItem, BOOL fEnable)
 {
    HWND hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
    HWND hwndMenu   = WinWindowFromID(hwndParent, FID_MENU);

    WinSendMsg(hwndMenu, MM_SETITEMATTR,
               MPFROM2SHORT(sMenuItem, TRUE),
               MPFROM2SHORT(MIA_DISABLED, fEnable ? 0 : MIA_DISABLED));
 }


 /*
  * Function name: EntryFldDlgProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              No user data is expected in the WM_CREATE.
  *
  * Returns: Terminates with a TRUE iff a new valid Tower Size has been entere
  *
  * Purpose: Handles all the messages associated with the set entry field
  *          and calls the appropriate handling procedures.  The purpose
  *          of this dialog box is to get a new number of disks for the
  *          hanoi routine.
  *
  *
  * Usage/Warnings: If the value entered is valid, global cTowerSize is
  *                 changed to the new value, and TRUE is returned.
  *
  * Calls:
  */

 MRESULT EXPENTRY EntryFldDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
 {
    SHORT sNewSize;            /* Holds new number of disks        */

    switch(msg)
    {
       case WM_INITDLG:
          WinSendDlgItemMsg(hwnd, ID_ENTRYFLD,EM_SETTEXTLIMIT,  /* Limit len *
                                  MPFROM2SHORT(2,0),NULL);
          WinSetDlgItemShort(hwnd, ID_ENTRYFLD,(SHORT) cTowerSize,TRUE);
          return 0L;                           /* Allow normal focus setting *

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:
                WinQueryDlgItemShort(hwnd, ID_ENTRYFLD,
                                     &sNewSize, TRUE); /* Get the short      *
                if(sNewSize>0 && sNewSize<=MAXDISKCNT) /* Set new Tower size *
                {
                   cTowerSize = (BYTE) sNewSize;
                   WinDismissDlg(hwnd,TRUE);
                }
                else                                   /* Invalid value      *
                   WinDismissDlg(hwnd,FALSE);
                return 0L;

             case DID_CANCEL:
                WinDismissDlg(hwnd,FALSE);
                return 0L;

             default:
                return WinDefDlgProc(hwnd, msg, mp1, mp2);
          }

       default:
          return WinDefDlgProc(hwnd, msg, mp1, mp2);
    }
 }

 /*
  * Function name: SetupTowers()
  *
  * Parameters:  None
  *
  * Returns: VOID
  *
  * Purpose: This routine initializes the global arrays that represent the
  *          hanoi stacks.  This involves placing all the disks on the
  *          first peg, emptying the other 2 pegs and setting the associated
  *          counts.
  *
  * Usage/Warnings: Calling uses the global variable cTowerSize to determine
  *                 how many disks there are.
  *
  * Calls:
  */

 VOID SetupTowers()
 {
    USHORT cnt;

    for(cnt=0;cnt<cTowerSize;cnt++)       /* Setup the intial post with disks
       abTowers[0][cnt] = (BYTE)(cTowerSize-cnt-1);

    abTowersNdx[0] = cTowerSize;          /* Set disk count for initial post
    abTowersNdx[1] = abTowersNdx[2] = 0;  /* Zero other post counts
 }



 HELLO.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\HELLO\HELLO.C

 /*
  * HELLO.C -- A simple program which calls the OpenDlg library
  * Created by Microsoft Corporation, 1989
  */
 #define INCL_PM
 #include <os2.h>
 #include <opendlg.h>
 #include "hello.h"
 /*
  * Globals
  */
 HAB     hAB;
 HMQ     hMqHello;
 HWND    hWndHello;
 HWND    hWndHelloFrame;
 CHAR    szClassName[]        = "Hello World";
 CHAR        szMessage[]        = " - File Dialog Sample";
 CHAR        szExtension[]        = "\\*.*";
 CHAR        szHelp[]        = "Help would go here.";
 DLF        vdlf;
 HFILE        vhFile;
 /*
  * Main routine...initializes window and message queue
  */
 int cdecl main( ) {
     QMSG qmsg;
     ULONG ctldata;

     hAB = WinInitialize(0);

     hMqHello = WinCreateMsgQueue(hAB, 0);

     if (!WinRegisterClass( hAB, (PCH)szClassName, (PFNWP)HelloWndProc,
                 CS_SIZEREDRAW, 0))
         return( 0 );

     /* Create the window */
     ctldata = FCF_STANDARD & ~(FCF_ACCELTABLE);
     hWndHelloFrame = WinCreateStdWindow( HWND_DESKTOP, WS_VISIBLE, &ctldata,
                                          szClassName, szMessage,
                                          WS_VISIBLE, (HMODULE) NULL, ID_RESOU
                                          (HWND FAR *)&hWndHello );

     WinShowWindow( hWndHelloFrame, TRUE );

     /* Poll messages from event queue */
     while( WinGetMsg( hAB, (PQMSG)&qmsg, (HWND)NULL, 0, 0 ) )
         WinDispatchMsg( hAB, (PQMSG)&qmsg );

     /* Clean up */
     WinDestroyWindow( hWndHelloFrame );
     WinDestroyMsgQueue( hMqHello );
     WinTerminate( hAB );
 }

 MRESULT CALLBACK HelloWndProc(hWnd, msg, mp1, mp2)
 /*
  * This routine processes WM_COMMAND, WM_PAINT.  It passes
  * everything else to the Default Window Procedure.
  */
 HWND hWnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     HPS                hPS;
     POINTL        pt;
     CHARBUNDLE        cb;
     RECTL        rcl;

     switch (msg) {

         case WM_COMMAND:
             switch (COMMANDMSG(&msg)->cmd) {

                 case IDM_OPEN: /* Demonstrate Open... dialog call */
                     SetupDLF( &vdlf
                             , DLG_OPENDLG
                             , &vhFile
                             , szExtension
                             , NULL
                             , "Open Title"
                             , szHelp );
                      DlgFile(hWndHelloFrame, &vdlf);
                     break;

                 case IDM_SAVE: /* Demonstrate Save As... dialog call */
                     SetupDLF( &vdlf
                             , DLG_SAVEDLG
                             , &vhFile
                             , szExtension
                             , NULL
                             , "Save Title"
                             , szHelp);
                     lstrcpy( (PSZ)vdlf.szOpenFile, (PSZ)"foo.bar");
                     DlgFile(hWndHelloFrame, &vdlf);
                     break;

                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hWnd, AboutDlgProc,
                               (HMODULE) NULL, IDD_ABOUT, NULL);
                     return 0;

                 default: break;
             }
             break;

         case WM_PAINT:
             /* Open the presentation space */
             hPS = WinBeginPaint(hWnd, NULL, &rcl);

             /* Fill the background with Dark Blue */
             WinFillRect(hPS, &rcl, CLR_DARKBLUE);

             /* Write "Hello World" in Red */
             pt.x = pt.y = 0L;
             cb.lColor = CLR_RED;
             GpiSetAttrs(hPS, PRIM_CHAR, CBB_COLOR, 0L, &cb);
             GpiCharStringAt(hPS, &pt, (LONG)sizeof(szClassName)-1, szClassNam

             /* Finish painting */
             WinEndPaint(hPS);
             break;

         default: return WinDefWindowProc(hWnd, msg, mp1, mp2); break;
     }
     return 0L;
 }

 MRESULT CALLBACK AboutDlgProc(hDlg, msg, mp1, mp2)
 /*
     About... dialog procedure
 */
 HWND hDlg;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch(msg) {
         case WM_COMMAND:
             switch(COMMANDMSG(&msg)->cmd) {
                 case DID_OK: WinDismissDlg(hDlg, TRUE); break;
                 default: break;
             }
         default: return WinDefDlgProc(hDlg, msg, mp1, mp2);
     }
     return FALSE;
 }


 INIEDIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\INIEDIT\INIEDIT.C

 /******************************* Module Header ******************************
 * Module Name: IniEdit.c
 *
 *
 * PM OS2.ini Editor
 *
 * Allows adding, deleting and modifying of os2.ini entries through PM
 * interface
 *
 *
 \***************************************************************************/


 #define LINT_ARGS                           // Include needed parts of PM
 #define INCL_WININPUT                       //    definitions
 #define INCL_WINSYS
 #define INCL_WINMESSAGEMGR
 #define INCL_WINBUTTONS
 #define INCL_WINPOINTERS
 #define INCL_WINHEAP
 #define INCL_WINSHELLDATA
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINDIALOGS
 #define INCL_WINLISTBOXES
 #define INCL_WINENTRYFIELDS
 #define INCL_DOSMEMMGR
 #define INCL_WINSWITCHLIST
 #define INCL_DOSPROCESS
 #define INCL_GPIBITMAPS
 #define INCL_GPIREGIONS
 #define INCL_GPILCIDS
 #define INCL_GPIPRIMITIVES
 #define INCL_DEV

 #include <string.h>
 #include <stdio.h>

 #include <os2.h>

 #include "IniEdit.h"


 /******************************* Constants **********************************

 #define STACK_SIZE            0x2000        // Stack size for second thread
 #define UPPER_SEGMENT_LIMIT   0xFD00        // Amount of Segment used

 /******************************** Globals **********************************/

 char szIniEdit[] = "IniEdit";               // App String Name

 HAB       habIniEdit;                       // Handle Anchor Block
 HMQ       hmqIniEdit;                       // Handle Message Queue
 HWND      hwndIniEdit;                      // Main Client Window
 HWND      hwndIniEditFrame;                 // Frame Window
 HDC       hdcScreen;                        // DC for Client Window
 HPS       hpsScreen;                        // PS for Client Window


 USHORT    cAppNames = 0;                    // Count of App names in os2.ini
 USHORT    usShift = 0;                      // DosHugeAlloc segment offsets
 HWND      FocusWindow = (HWND)NULL;         // Focus of Dialog Box
 USHORT    cxBorder;                         // System border width
 USHORT    cyBorder;                         // System border height

 USHORT    usFormat = APP_FORM;              // Current Display format
 USHORT    usPrintFormat = APP_FORM;         // Format for Printing
 USHORT    usLineHeight = 12;                // Current font Height
 HWND      hwndList = (HWND)NULL;            // Handle of Main ListBox
 HWND      hwndMenu = (HWND)NULL;            // Handle of Main Menu

 PGROUPSTRUCT  pGroups;                      // Pointer to String Groups
 PPAIRSTRUCT   pPairsBase;                   // Pointer to Key-Value Pairs
 PPAIRSTRUCT   pPairsAlloc;                  // Pointer to next Avail Memory
 PBYTE         pPrintStack;                  // Pointer to Print Thread Stack

 #define HOLD_LEN 4096
 CHAR          achNames[HOLD_LEN];           // Array of Character from Query
 CHAR          szBuf[2 * MAX_STRING_LEN];        // Character buffer for Pairs


 /***************************** Function Decls ******************************/

 VOID    ProcessMenuItem( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2 );
 VOID    cdecl main( VOID );
 VOID    IniEditPaint( VOID );
 VOID    ReadIni( VOID );
 VOID    OldProfilePrint( VOID );
 VOID    UpdateListBox( BOOL fRead, USHORT usForm );

 MRESULT _loadds EXPENTRY IniEditWndProc(HWND, USHORT, MPARAM, MPARAM);


 /***************************** Function Header *****************************\
 *
 * main
 *
 *
 * Do initialization then do a message loop
 *
 \***************************************************************************/

 VOID cdecl main()
 {

     QMSG         qmsg;                      // Current Queue Message
     ULONG        fcf;                       // Frame Control Flags
     SIZEL        sizel;                     // Size of PS
     RECTL        rclWindow;                 // Size Rect for ListBox Window
     SEL          sel;                       // Selector of allocated segments
     SWCNTRL      swcntrl;                   // Switch Control Block
     FONTMETRICS  fmetrics;                  // FontMetrics of current font


     /*** Set up and Initialization ***/

     /* Initialize the anchor block handle */
     habIniEdit = WinInitialize(0);

     /* Create the message queue */
     hmqIniEdit = WinCreateMsgQueue(habIniEdit, 0);

     /* Register the window class for the IniEdit window */
     WinRegisterClass(habIniEdit, (PCH)szIniEdit, IniEditWndProc,
             CS_SIZEREDRAW, 0);

     /* Create the window for IniEdit */
     fcf = FCF_TITLEBAR | FCF_MINMAX | FCF_SYSMENU | FCF_SIZEBORDER | FCF_MENU
         | FCF_SHELLPOSITION | FCF_ACCELTABLE | FCF_ICON;

     hwndIniEditFrame = WinCreateStdWindow( HWND_DESKTOP, WS_VISIBLE,
         (PVOID)&fcf, (PSZ)szIniEdit, (PSZ)szIniEdit, WS_VISIBLE,
         (HMODULE)NULL, IDI_INIEDIT, (PHWND)&hwndIniEdit);

     /* Create a DC for the IniEdit window */
     hdcScreen = WinOpenWindowDC(hwndIniEdit);

     /* also create a screen PS */

     sizel.cx= 0L;   // To use the default screen page size.
     sizel.cy= 0L;

     if( (hpsScreen = GpiCreatePS( habIniEdit, hdcScreen, &sizel,
             (PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC ))) == (HPS)NUL
         {
                 ;
         }


     /* Initially set the keyboard focus to us */
     WinSetFocus(HWND_DESKTOP, hwndIniEdit);

     /* get the font size */
     GpiQueryFontMetrics( hpsScreen, (LONG)sizeof( FONTMETRICS ), &fmetrics );
     usLineHeight = (USHORT)(fmetrics.lMaxDescender + fmetrics.lMaxBaselineExt

     /* get the system widths of a border */
     cxBorder = (USHORT) WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER);
     cyBorder = (USHORT) WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER);

     /* this menu handle is often used */
     hwndMenu = WinWindowFromID( hwndIniEditFrame, FID_MENU );

     /* add program to switch list */
     swcntrl.hwnd             = hwndIniEditFrame;
     swcntrl.hwndIcon         = NULL;
     swcntrl.hprog            = NULL;
     swcntrl.idProcess        = 0;
     swcntrl.idSession        = 0;
     swcntrl.uchVisibility    = 0;
     swcntrl.fbJump           = 0;
     strcpy( swcntrl.szSwtitle, szIniEdit);
     swcntrl.fReserved        = 0;

     WinAddSwitchEntry( &swcntrl );

     /* Create main list box in main window */
     WinQueryWindowRect( hwndIniEdit, &rclWindow);
     rclWindow.yTop -= usLineHeight;
     rclWindow.yTop += cyBorder;
     rclWindow.xRight += 2*cxBorder;
     hwndList = WinCreateWindow( hwndIniEdit,            // parent
                                 WC_LISTBOX,             // class
                                 (PSZ)"Scroll",          // name
                                 LS_NOADJUSTPOS,         // style
                                 -cxBorder, -cyBorder,   // position
                                 (USHORT)rclWindow.xRight,
                                 (USHORT)rclWindow.yTop,
                                 hwndIniEditFrame,       // Owner
                                 HWND_TOP,               // InsertBehind
                                 IDI_LIST,               // ID
                                 (PVOID)NULL,            // pCtlData,
                                 (PVOID)NULL);


     /*** Memory Allocation ***/

     /* Alloc the needed space for the groups */
     if( DosAllocSeg( 32000, &sel, 0) )
         ErrMessage( "main: DosAlloc for pGroup failed" );
     pGroups = MAKEP( sel, 0);

     if( DosAllocHuge( 4, 0, &sel, 0, 0) )
         ErrMessage( "main: DosAlloc for pPairs failed" );
     pPairsAlloc = pPairsBase = MAKEP( sel, 0);

     /* create a stack for second thread */
     if( DosAllocSeg( STACK_SIZE, &sel, 0) )
         ErrMessage( "main: DosAlloc for Stack failed" );
     pPrintStack = MAKEP( sel, 0);

     DosGetHugeShift( &usShift );

     /* read in os2.ini and fill in list box */
     UpdateListBox( TRUE, APP_FORM );

     WinShowWindow( hwndList, TRUE );

     /* Process messages for the window */
     while ( WinGetMsg(habIniEdit, (PQMSG)&qmsg, (HWND)NULL, 0, 0 ) )
         {

         /* Dispatch the message */
         WinDispatchMsg(habIniEdit, (PQMSG)&qmsg);
         }


     /*** CleanUp ***/

     /* Destroy the IniEdit window and message queue */
     GpiDestroyPS( hpsScreen );
     WinDestroyWindow(hwndIniEditFrame);
     WinDestroyMsgQueue(hmqIniEdit);

     /* Exit PM */
     WinTerminate( habIniEdit );
     DosExit( EXIT_PROCESS, 0 );

 }  /* main */


 /****************************** Function Header ****************************\
 *
 * ReadIni
 *
 *
 * Reads in OS2.ini
 *
 \***************************************************************************/

 VOID ReadIni()
 {
     USHORT    cchNames;                     // Count of Character from Query
     USHORT    Index[MAX_APP_NAMES];         // Index of Names into achNames
     USHORT    cPairs;                       // Count of pairs in current AppN
     ULONG     ul;
     USHORT    i,j;                          // Loop Counters


     /* Reset Count of App Names */
     cAppNames = 0;

     /* Reset memory available pointer to Base */
     pPairsAlloc = pPairsBase;

     /* Determine number of characters in app Names Strings */
     WinQueryProfileSize( habIniEdit, NULL, NULL, &cchNames );

     /* Read in the App Name strings */
     WinQueryProfileString( habIniEdit, NULL, NULL, " ", achNames, cchNames );

     /*** Find the starting index of each App ***/

     /* step through each string in set of app characters
      * adding length of current string to find begining of next string
      * also store each App Name into szAppName element of Group
      */
     for( i=0; i<cchNames; i += (strlen(pGroups[cAppNames-1].szAppName)+1) )
         {
         if( achNames[i] != (char)0 )
             {
             strcpy( pGroups[cAppNames++].szAppName, &achNames[i]);
             }  /* if */
         else
             if( achNames[i+1] == (char)0 )
                 break;
         }  /* for */


     /*** Read elements of each App Name ***/
     for( i=0; i<cAppNames; i++ )
         {
         /* Get number of Character Associated with App Name */
         WinQueryProfileSize( habIniEdit, pGroups[i].szAppName, NULL, &cchName

         /* Enumerate all KeyNames for this app name */
         WinQueryProfileString( habIniEdit, pGroups[i].szAppName, NULL, " ", a

         /* Count the number of key Names */
         cPairs = 0;
         for( j=0; j<cchNames; j++)
             if( achNames[j] != (CHAR)0 )
                 {
                 Index[cPairs++] = j;
                 j += strlen( &achNames[j] );
                 }

         pGroups[i].cKeys = cPairs;

         /*
          * Make sure we can fit the entire structure into our current
          * segment, if not, lets jump to the next segment
          */
         ul =  sizeof(PAIRSTRUCT) * cPairs;
         if ((ul + (ULONG)OFFSETOF(pPairsAlloc)) >= 0x10000L)
             pPairsAlloc = MAKEP( (HIUSHORT(pPairsAlloc)+(1<<usShift)), 0);



         /* Allocate the number of pair structures for the current group */
         pGroups[i].pPairs = pPairsAlloc;

         // pPairsAlloc += sizeof(PAIRSTRUCT)*cPairs;
         // Remember that incrementing a pointer automatically mult by size of
         pPairsAlloc += cPairs;

         /* Step to next segment if near end of current segment */
         if( LOUSHORT(pPairsAlloc) > UPPER_SEGMENT_LIMIT)
             {
             pPairsAlloc = MAKEP( (HIUSHORT(pPairsAlloc)+(1<<usShift)), 0);
             }

         /* Store the KeyName into the pair structure */
         for( j=0; j<cPairs; j++ )
             {
             strcpy( pGroups[i].pPairs[j].szKey, &achNames[Index[j]] );

             /* store the key value */
             WinQueryProfileString( habIniEdit, pGroups[i].szAppName,
                      pGroups[i].pPairs[j].szKey, " ",
                      pGroups[i].pPairs[j].szValue, MAX_STRING_LEN );

             }
         }  /* each App Name */

 }  /* ReadIni */


 /****************************** Function Header ****************************\
 *
 * ProcessMenuItem
 *
 *
 * Act on the corresponding Menu Item Choosen
 *
 \***************************************************************************/

 VOID ProcessMenuItem( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2 )
 {
     TID  Tid;                               // ID of new thread; Not used


     /* Switch on the Menu Item choosen */
     switch( LOUSHORT( mp1 ) )
         {
         case IDMI_SHOW_ALL:
         case IDMI_SHOW_APPNAMES:
             usFormat = (LOUSHORT(mp1) == IDMI_SHOW_ALL);
             UpdateListBox( FALSE, usFormat ? ALL_FORM : APP_FORM );
             break;

         case IDM_SEARCH:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)SearchWndProc,
                             (HMODULE)NULL, IDD_SEARCH, (PVOID)NULL);
             break;

         case IDMI_EDIT_DELETE_KEY:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)DelKeyWndProc,
                             (HMODULE)NULL, IDD_DEL_KEY, (PVOID)NULL);
             UpdateListBox( TRUE, usFormat ? ALL_FORM : APP_FORM );
             break;

         case IDMI_EDIT_DELETE_APP:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)DelAppWndProc,
                             (HMODULE)NULL, IDD_DEL_APP, (PVOID)NULL);
             UpdateListBox( TRUE, usFormat ? ALL_FORM : APP_FORM );
             break;

         case IDMI_EDIT_ADD_KEY:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)AddKeyWndProc,
                             (HMODULE)NULL, IDD_ADD_KEY, (PVOID)NULL);
             UpdateListBox( TRUE, usFormat ? ALL_FORM : APP_FORM );
             break;

         case IDMI_EDIT_CHANGE:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)ChangeKeyWndProc
                             (HMODULE)NULL, IDD_CHANGE_KEY, (PVOID)NULL);
             UpdateListBox( TRUE, usFormat ? ALL_FORM : APP_FORM );
             break;

         case IDMI_PRINT_ALL:
         case IDMI_PRINT_APP:
             usPrintFormat = LOUSHORT(mp1) == IDMI_PRINT_ALL ? ALL_FORM : APP_
             if( DosCreateThread( PrintThread, &Tid, ((PBYTE)(pPrintStack)+STA
                 ErrMessage("StartThread2: DosCreateThread Failed");
             break;

         case IDMI_REFRESH:
             UpdateListBox( TRUE, usFormat );
             break;

         case IDMI_ABOUT:
             WinDlgBox(HWND_DESKTOP, hwndIniEditFrame, (PFNWP)DelAppWndProc,
                             (HMODULE)NULL, IDD_ABOUT, (PVOID)NULL);
             break;

         default:
             WinDefWindowProc(hwnd, msg, mp1, mp2);

             break;

         }  /* switch */

 }  /* ProcessMenuItem */


 /****************************** Function Header ****************************\
 *
 * UpdateListBox
 *
 *
 * Update Main List Box to correct state
 *    May Also:
 *    - Check correct menu item
 *    - Repaint title of List Box
 *    - ReRead os2.ini file
 *
 \***************************************************************************/

 VOID UpdateListBox( BOOL fReadIni, USHORT usNewFormat )
 {
     USHORT    i,j;                          // Loop Counters
     USHORT    Index;                        // Index into ListBox
     static    USHORT    usLastFormat = -1;  // Last displayed format


     /* Check the correct item if format changed */
     if( usLastFormat != usNewFormat )
         {
         WinSendMsg( hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDMI_SHOW_ALL, TRU
                 MPFROM2SHORT(MIA_CHECKED, usFormat ? MIA_CHECKED:FALSE));

         WinSendMsg( hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDMI_SHOW_APPNAMES
                 MPFROM2SHORT(MIA_CHECKED, (!usFormat) ? MIA_CHECKED:FALSE));
         usLastFormat = usNewFormat;

         WinSendMsg( hwndIniEdit, WM_PAINT, (MPARAM)NULL, (MPARAM)NULL );
         }


     /* Turn off list box updates */
     WinEnableWindowUpdate( hwndList, FALSE );

     /* Remove all items from list box */
     WinSendMsg( hwndList, LM_DELETEALL, (MPARAM)0, (MPARAM)0 );

     /* ReRead os2.ini if needed */
     if( fReadIni )
         ReadIni();

     /* Add elements to listbox */
     if( usNewFormat == ALL_FORM )
         {

         /* Insert all app Names */
         for( i=0; i<cAppNames; i++ )
             {
             Index = SHORT1FROMMR(WinSendMsg( hwndList, LM_INSERTITEM,
                     MPFROM2SHORT(LIT_END, NULL),
                     MPFROMP(pGroups[i].szAppName) ));

             WinSendMsg( hwndList, LM_SETITEMHANDLE,
                     MPFROMSHORT(Index),
                     MPFROMSHORT(i) );

             /* Insert Key Value Pairs for App Name */
             for( j=0; j<pGroups[i].cKeys; j++ )
                 {
                 sprintf( szBuf, "     %s: %s", pGroups[i].pPairs[j].szKey,
                         pGroups[i].pPairs[j].szValue );
                 Index = SHORT1FROMMR(WinSendMsg( hwndList, LM_INSERTITEM,
                         MPFROM2SHORT(LIT_END, NULL),
                         MPFROMP(szBuf) ));

                 WinSendMsg( hwndList, LM_SETITEMHANDLE,
                         MPFROMSHORT(Index),
                         MPFROM2SHORT(i,j) );

                 }
             }
         }  /* if */
     else
         {
         /* Insert all app Names */
         for( i=0; i<cAppNames; i++ )
             {
             WinSendMsg( hwndList, LM_INSERTITEM,
                     MPFROM2SHORT(LIT_SORTASCENDING, NULL),
                     MPFROMP(pGroups[i].szAppName) );
             }
         }  /* else */

     /* Do All repainting of ListBox */
     WinEnableWindowUpdate( hwndList, TRUE );

 }  /* UpdateListBox */


 /****************************** Function Header ****************************\
 *
 * IniEditPaint
 *
 *
 * Window Paint Routine
 *
 \***************************************************************************/

 VOID IniEditPaint()
 {
     RECTL     rclWindow;                    // Current size of Main Window
     RECTL     rclBlit;                      // Size of Area to Blank for Titl
     CHAR      szShowMode[MAX_STRING_LEN];   // String Description of mode


     /* Get the size of the whole window */
     WinQueryWindowRect( hwndIniEdit, &rclWindow );

     /* Paint the window Title Area */
     rclBlit = rclWindow;
     rclBlit.yBottom = rclBlit.yTop - usLineHeight;

     GpiBitBlt( hpsScreen, (HPS)NULL, 2L, (PPOINTL)&rclBlit, ROP_ONE, (LONG)NU

     /* Write the Title */
     strcpy( szShowMode, usFormat == APP_FORM ? SZAPP : SZALL );
     WinDrawText( hpsScreen, strlen(szShowMode), szShowMode, &rclWindow,
             CLR_BLUE, CLR_WHITE, DT_CENTER|DT_TOP);

 }  /* IniEditPaint */


 /****************************** Function Header ****************************\
 *
 * IniEditWndProc
 *
 *
 * Window Proc for IniEdit
 *
 \***************************************************************************/

 MRESULT _loadds EXPENTRY IniEditWndProc(HWND hwnd, USHORT msg,
         MPARAM mp1, MPARAM mp2)
 {

     CHAR      szBuf[MAX_STRING_LEN];        // Input character Buffer
     CHAR      szBuf2[MAX_STRING_LEN];       // Second Input Character Buffer
     USHORT    Index;                        // Index of Current ListBox Item
     USHORT    TopIndex;                     // Current Top Item in ListBox
     ULONG     Handle;                       // ListBox Item Handle Info
     HWND      hwndDialog;                   // Window handle of Dailog Box
     HWND      hwndText;                     // Handle of current text window
     HPS       hpsPaint;                     // PS to Paint
     RECTL     rclPaint;                     // Rect in hpsPaint to Paint
     BOOL      fScroll = FALSE;              // Scroll List Box Flag


     /* Switch on message being processed */
     switch( msg )
         {
         case WM_PAINT:
             /* Paint the IniEdit window portion not covered by List Box */
             hpsPaint = WinBeginPaint(hwnd, (HPS)NULL, &rclPaint);
             IniEditPaint();
             WinEndPaint(hpsPaint);
             break;

         case WM_COMMAND:
             /* If menu item call Processing Routine */
             if( LOUSHORT( mp2 ) == CMDSRC_MENU )
                 ProcessMenuItem( hwnd, msg, mp1, mp2 );

             /* If accelorator call appropriate routine */
             if( LOUSHORT( mp2 ) == CMDSRC_ACCELERATOR )
                 {
                 switch( LOUSHORT( mp1 ) )
                     {
                     case IDDI_SEARCH_NEXT:
                         FindNext();
                         break;
                     }
                 }
             break;

         case WM_SIZE:
             /* Put the list box in the correct location of the window */
             if( hwndList != (HWND)NULL )
                 /* The position is set to fill the client, except for the */
                 /* area at the top for some text.  In addition, the       */
                 /* rectangle is outset by a border width on all dimensions*/
                 /* except for the top so that the list box border is      */
                 /* "tucked" under the clients border and doesn't cause    */
                 /* there to be a double thick border around it.           */
                 WinSetWindowPos( hwndList, HWND_TOP, -cxBorder, -cyBorder,
                         SHORT1FROMMP(mp2)+(2*cxBorder),
                         SHORT2FROMMP(mp2)-usLineHeight + cyBorder,
                         SWP_SIZE | SWP_MOVE );
             break;

         case WM_CONTROL:
             /* Switch on Control activated */
             switch( SHORT1FROMMP(mp1) )
                 {

                 /*** Process List Box Activity ***/
                 case IDI_LIST:
                     /* was it a double click? */
                     if( SHORT2FROMMP(mp1) == LN_ENTER )
                         {
                         /* get the item clicked on */
                         Index = SHORT1FROMMR(WinSendMsg( hwndList, LM_QUERYSE
                                 (MPARAM)0, (MPARAM)0 ));

                         /* grab its text */
                         WinSendMsg( hwndList, LM_QUERYITEMTEXT,
                                 MPFROM2SHORT(Index, MAX_STRING_LEN), MPFROMP(

                         /* if in APP form toggle to ALL form */
                         if( usFormat == APP_FORM )
                             {
                             usFormat = ALL_FORM;
                             fScroll = TRUE;
                             }
                         else
                             {
                             /* if an App name was choosen then go to APP form
                             if( szBuf[0] != ' ')
                                 {
                                 usFormat = APP_FORM;
                                 fScroll = TRUE;
                                 }
                             else
                                 /* A Key Value Pair was double clicked
                                  * allow editing of key Value
                                  */
                                 {

                                 FocusWindow = (HWND)1;

                                 hwndDialog = WinLoadDlg( HWND_DESKTOP,
                                     hwndIniEditFrame, ChangeKeyWndProc,
                                     (HMODULE)NULL, IDD_CHANGE_KEY, NULL);

                                 Handle = (ULONG)WinSendMsg( hwndList, LM_QUER
                                     MPFROMSHORT(Index), (MPARAM)NULL );

                                 hwndText = WinWindowFromID( hwndDialog, IDDI_
                                 WinSendMsg(hwndText, EM_SETTEXTLIMIT,
                                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                                 WinSetWindowText( hwndText, pGroups[LOUSHORT(

                                 /* note bug in PMWin GPs if full segment */
                                 hwndText = WinWindowFromID( hwndDialog, IDDI_
                                 WinSendMsg(hwndText, EM_SETTEXTLIMIT,
                                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                                 strcpy( szBuf2, pGroups[LOUSHORT(Handle)].pPa
                                 WinSetWindowText( hwndText, szBuf2 );

                                 hwndText = WinWindowFromID( hwndDialog, IDDI_
                                 WinSendMsg(hwndText, EM_SETTEXTLIMIT,
                                         MPFROMSHORT(MAX_STRING_LEN), 0L);
                                 strcpy( szBuf2, pGroups[LOUSHORT(Handle)].pPa
                                 WinSetWindowText( hwndText, szBuf2 );

                                 WinPostMsg( hwndText, EM_SETSEL,
                                         MPFROM2SHORT(0, strlen(szBuf2)), (MPA

                                 if( WinProcessDlg( hwndDialog ) == IDDI_CHANG
                                     {
                                     TopIndex = SHORT1FROMMR(WinSendMsg( hwndL
                                          (MPARAM)NULL, (MPARAM)NULL ));

                                     UpdateListBox( TRUE, usFormat );

                                     /* scroll to top */
                                     WinSendMsg( hwndList, LM_SETTOPINDEX,
                                          MPFROMSHORT(TopIndex), (MPARAM)NULL

                                     /* make the item selected */
                                     WinSendMsg( hwndList, LM_SELECTITEM,
                                             MPFROMSHORT(Index), MPFROMSHORT(T

                                     /* make selected */
                                     }

                                 WinDestroyWindow( hwndDialog );
                                 }
                             }

                         /* Make the double clicked item selected in new form
                         if( fScroll )
                             {
                             /* put in correct form */
                             UpdateListBox( FALSE, usFormat );

                             /* get the index of the item clicked on */
                             Index = SHORT1FROMMR(WinSendMsg( hwndList, LM_SEA
                                     MPFROM2SHORT(LSS_SUBSTRING, LIT_FIRST),
                                     MPFROMP(szBuf) ));

                             /* scroll that item to the top */
                             WinSendMsg( hwndList, LM_SETTOPINDEX,
                                     MPFROMSHORT(Index), (MPARAM)NULL );

                             /* make the item selected */
                             WinSendMsg( hwndList, LM_SELECTITEM,
                                     MPFROMSHORT(Index), MPFROMSHORT(TRUE) );
                             }
                         }  /* if ENTER */
                 }
             break;

         default:
             return WinDefWindowProc(hwnd, msg, mp1, mp2);
             break;
         }

     return 0L;

 }  /* IniEditWndProc */


 INIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\INIT.C

 /***************************************************************************\
 * INIT.C -- Library initialization funcitons
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #include "tool.h"
 /****************************************************************************
 * This function initializes the file dialog library (by loading strings).
 *
 * Note: Initialization will fail if CCHSTRINGSMAX is smaller than the
 *       space taken up by all strings in the .rc file.  Fix by increasing
 *       CCHSTRINGSMAX in wintool.h and maybe also the initial heap size
 *       in wintool.def.
 *
 * Returns:
 *   TRUE if initialization successful
 *   FALSE otherwise
 \***************************************************************************/

 BOOL PASCAL InitLibrary()
 {
     int i;
     int cch;
     PSTR pch;
     PSTR pmem;
     int cchRemaining;

     /* allocate memory for strings */
     if (!(pch = (pmem = WinAllocMem(vhheap, cchRemaining = CCHSTRINGSMAX))))
         return FALSE;

     /* load strings from resource file */
     for (i = 0; i < CSTRINGS; i++) {
         cch = 1 + WinLoadString(HABX, vhModule, i, cchRemaining, (PSZ)pch);
         if (cch < 2)
             /* loadstring failed */
             return FALSE;
         vrgsz[i] = pch;
         pch += cch;

         if ((cchRemaining -= cch) <= 0)
             /* ran out of space */
             return FALSE;
     }

     /* reallocate string space to size actually needed */
     WinReallocMem(vhheap, pmem, CCHSTRINGSMAX, CCHSTRINGSMAX - cchRemaining);

     return TRUE;
 }


 JIGSAW.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\JIGSAW\JIGSAW.C

 /********************************** Jigsaw  *********************************
 /*
 /* Created 1988, Microsoft Corporation.
 /*
 /* Purpose:  To illustrate the use of Gpi retained segments.
 /*
 /* Summary:  This program provides a jigsaw puzzle, based on a decomposition
 /*   of an arbitrary bitmap loaded from a file.  The user can jumble the
 /*   pieces, then drag them individually by means of the mouse.  The image
 /*   can be zoomed in and out and scrolled up/down and left/right.
 /*
 /*   Each piece of the puzzle is a retained segment.  When a piece is
 /*   selected for dragging, it is made dynamic.  A menu option allows the
 /*   selected piece to be dragged as an outline or as a normal-looking piece.
 /*
 /*   Individual pieces are made to "move" by changing their model transforms.
 /*   Scrolling and zooming of the whole picture is done by changing the
 /*   default viewing transform.
 /*
 /* Optimizations:  While it is possible to implement this puzzle using a very
 /*   naive approach, this is liable to lead to a rather slowly-operating
 /*   program.  The following optimizations dramatically improve program
 /*   performance:
 /*
 /*   1> BitBlt only as much of the bitmap through a clip path as necessary.
 /*   Each piece of the puzzle is drawn by defining a clip path, blitting
 /*   through the path, and drawing an outline on the same path.  The naive
 /*   approach is to blit the whole bitmap through the clip path.  A more
 /*   clever approach is to compute the piece's bounding box and only use
 /*   the source and destination rectangles which correspond to this box.
 /*   This leads to an order-of-magnitude speedup in the time to draw one
 /*   piece.
 /*
 /*   2> Make the source and target rectangles for BitBlt the same size
 /*   in device coordinates.  A BitBlt in a retained segment must be done
 /*   with GpiWCBitBlt and the target rectangle must be specified in world
 /*   coordinates, so you must use GpiConvert (taking into account that in
 /*   world space rectangles are inclusive-inclusive while in device space
 /*   rectangles are inclusive-exclusive) to compute what target world space
 /*   rectangle will be converted to the desired device space rectangle.
 /*   Making the sizes of the source and converted target rectangles differ
 /*   by even one pel will cause strectching or compression to occur, with
 /*   a dramatic loss in speed.        Unfortunately, due to rounding effects,
 /*   not always possible to guarantee that adding an offset to the
 /*   transformation applied to a segment will leave the size of the
 /*   rectangle defined by the orders in the segment unchanged.
 /*
 /*   3> Use auxiliary information to reduce the number of segments which
 /*   must be checked for correlation.  The naive approach to hit-testing is
 /*   to test the whole chain, even though generally only a small fraction of
 /*   the segments in the chain could possibly get a hit.  A more clever
 /*   approach is to take the bounding box for each segment and only include
 /*   the segment in the correlation check if the box contains the correlation
 /*   point.  eg.
 /*    a> Edit the chain by adjusting the ATTR_CHAINED attribute of each
 /*    segment to reflect candidacy for being hit.  Afterwards, fix up by
 /*    adding back removed segments to the chain.
 /*    b> Even faster is to keep an auxiliary data structure which records
 /*    the priority of the segments (placed in the SEGLIST chain).  Run
 /*    through the priority list from high-priority to low-priority and do a
 /*    correlation test on each segment which passes the bounding-box test.
 /*
 /*   4> When repainting through a clip region, only draw those segments which
 /*   overlap the clip region.  The naive approach is to set up the clip
 /*   region and do a GpiDrawChain on the whole chain.  The drawback to this
 /*   is that much time will be spent running through the orders in segments
 /*   which are not visible through the clip region.  Very often, most of the
 /*   segments in the picture can be eliminated from needing to be drawn by
 /*   recognizing that there is no overlap between the bounding boxes of the
 /*   segment and the clip region.
 /*
 /*   5> Do WinScrollWindow horizontally in multiples of 8 pels when possible.
 /*   For example, horizontal scrolls by 7 or 9 pels are much slower than a
 /*   a horizontal scroll by 8 pels.
 /*
 /****************************************************************************

 #define INCL_BITMAPFILEFORMAT

 #define INCL_DOSPROCESS
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSMEMMGR

 #define INCL_DEV

 #define INCL_WINWINDOWMGR
 #define INCL_WINMESSAGEMGR
 #define INCL_WININPUT
 #define INCL_WINRECTANGLES
 #define INCL_WINPOINTERS
 #define INCL_WINMENUS
 #define INCL_WINSCROLLBARS
 #define INCL_WINFRAMEMGR
 #define INCL_WINSWITCHLIST
 #define INCL_WINSYS

 #define INCL_GPIBITMAPS
 #define INCL_GPICONTROL
 #define INCL_GPITRANSFORMS
 #define INCL_GPIPRIMITIVES
 #define INCL_GPIMETAFILES
 #define INCL_GPIPATHS
 #define INCL_GPIREGIONS
 #define INCL_GPISEGMENTS
 #define INCL_GPISEGEDITING
 #define INCL_GPICORRELATION
 #define INCL_GPILCIDS

 #define INCL_ERRORS

 #include <os2.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <opendlg.h>
 #include "noncomm.h"
 #include "jigsaw.h"



 /*----------------------- inter-thread messages -----------------------------

 UM_DIE              WM_USER+1        /* instruct async thread to terminate  *
 UM_DRAW       WM_USER+2        /* draw the current picture              */
 UM_VSCROLL    WM_USER+3        /* perform scroll by recalculating the */
                                        /* default viewing transform
 UM_HSCROLL    WM_USER+4        /* perform scroll by recalculating the */
                                        /* default viewing transform
 UM_SIZING     WM_USER+5        /* perform sizing by recalculating the */
                                        /* default viewing transform
 UM_ZOOM_IN    WM_USER+6        /* zoom the picture by recalculating   */
                                        /* the default viewing transform
 UM_ZOOM_OUT   WM_USER+7        /* zoom the picture by recalculating   */
                                        /* the default viewing transform
 #define UM_REDRAW     WM_USER+8
 #define UM_JUMBLE     WM_USER+9
 #define UM_LOAD       WM_USER+10
 UM_DUMMY      WM_USER+11       /* all commands not forcing redraw     */
                                        /* must come after this one

 UM_LEFTDOWN   WM_USER+12       /* mouse button down in the client area*/
                                        /* perform a correlation on the curren
                                        /* picture, setting any picked segment
                                        /* to dynamic
 UM_MOUSEMOVE  WM_USER+13       /* mousemove command, remove, repositon*/
                                        /* and redraw any dynamic sements
 UM_LEFTUP     WM_USER+14       /* mouse button up in the client area  */
                                        /* set any dynamic segment to normal
 UM_FASTDRAG   WM_USER+15       /* toggle fast-drag (outline) mode     */
 UM_DRAWDONE   WM_USER+16       /* Async DrawChain has completed       */
 #define UM_FLUSH      WM_USER+17


 /*------------------------ element label values  ----------------------------

 #define FILLPATH       222L
 #define BITBLT_TOP     232L
 #define BITBLT_BOTTOM  233L


 /*------------------------- correlation parameters --------------------------

 HITS        1L                       /* maximum number of hits to return    *
 DEPTH        2L                       /* max depth of seg calls to return


 /*-------------------------- general definitions ----------------------------


 HAB        habMain=NULL;                       /* main thread anchor block ha
 HMQ        hmqMain=NULL;                       /* main thread queue handle
 HWND    hwndFrame=NULL;                /* frame control handle
 HWND        hwndClient=NULL;               /* client area handle
 HDC        hdcClient=NULL;                /* window dc handle
 HPS        hpsClient=NULL;                /* client area Gpi ps handle
 SIZEL        sizlMaxClient;                       /* max client area size
 HPS     hpsPaint=NULL;                 /* ps for use in Main Thread
 HRGN        hrgnInvalid = NULL;               /* handle to the invalid region

 HAB        habAsync=NULL;                       /* async thread anchor block
 HMQ        hmqAsync=NULL;                       /* async thread queue handle
 TID     tidAsync;                      /* async thread id
 SEL        selStack;                       /* async thread stack selector
 STACKSIZE  4096                /* async thread stack size              */
 SHORT        sPrty = -1;                       /* async thread priority

 HWND        hwndHorzScroll=NULL;               /* horizontal scroll bar windo
 HWND        hwndVertScroll=NULL;               /* vertical scroll bar window
 POINTS        ptsScrollPos, ptsOldScrollPos;
 POINTS        ptsScrollMax, ptsHalfScrollMax;
 POINTS        ptsScrollLine = { 8, 8};
 POINTS        ptsScrollPage = { 64, 64};

 #define UNITY               65536L
 MATRIXLF matlfIdentity = { UNITY, 0, 0, 0, UNITY, 0, 0, 0, 1 };
 LONG        lScale = 0;                       /* current zoom level
 #define ZOOM_MAX       8
 #define ZOOM_IN_ARG    1
 #define ZOOM_OUT_ARG   -1

 #define CALLSEG_BASE   1000
 POINTL        ptlOffset;
 POINTL        ptlBotLeft  = { 0, 0};
 POINTL        ptlTopRight = { 300, 300};
 LONG        lLastSegId;                       /* last segment id assigned to
 LONG        lPickedSeg;                       /* seg id of piece selected for
 RECTL        rclBounds;                       /* pict bounding box in model c
 POINTL        ptlOldMouse = {0L, 0L};        /* current mouse posn
 BOOL        fButtonDown = FALSE;               /* only drag if mouse down
 BOOL        fFastDrag = FALSE;               /* show only outline of dragging


 /*-------------------------- segment list -----------------------------------

 typedef struct _SEGLIST {               /* sl
     LONG                  lSegId;
     struct _SEGLIST FAR * pslPrev;
     struct _SEGLIST FAR * pslNext;
     POINTL                  ptlLocation; /* piece location, world coordinates
     RECTL                  rclCurrent;  /* segment bounding box, model coords
     RECTL                  rclBitBlt;   /* segment bounding box, world coords
 } SEGLIST ;
 typedef SEGLIST FAR *PSEGLIST;               /* psl
 typedef PSEGLIST FAR *PPSEGLIST;       /* ppsl
 PSEGLIST pslHead = NULL;               /* head of the list
 PSEGLIST pslTail = NULL;               /* tail of the list
 PSEGLIST pslPicked = NULL;               /* picked segment's list member
 #define   ADD_HEAD_SEG         1
 #define   ADD_TAIL_SEG         2
 #define        DEL_SEG         3

 /*-------------------------- bitmap-related data ----------------------------

 typedef struct _LOADINFO {               /* li
     HFILE   hf;
     CHAR    szFileName[MAX_FNAME_LEN];
 } LOADINFO ;
 typedef LOADINFO FAR *PLOADINFO;       /* pli

 HPS                   hpsBitmapFile=NULL, hpsBitmapTemp=NULL, hpsBitmapDrag=N
 HDC                   hdcBitmapFile=NULL, hdcBitmapTemp=NULL, hdcBitmapDrag=N
 HBITMAP            hbmBitmapFile=NULL, hbmBitmapTemp=NULL, hbmBitmapDrag=NULL
 BITMAPINFOHEADER   bmpBitmapFile   = {12L, 0, 0, 0, 0};
 BITMAPINFOHEADER   bmpBitmapTemp   = {12L, 0, 0, 0, 0};
 BITMAPINFOHEADER   bmpBitmapDrag   = {12L, 0, 0, 0, 0};
 BITMAPINFO           bmiBitmap           = {12L, 0, 0, 0, 0, {{0, 0, 0}}};
 static DEVOPENSTRUC dop = { NULL
                           , "DISPLAY"
                           , NULL
                           , NULL
                           , NULL
                           , NULL
                           , NULL
                           , NULL
                           , NULL };


 /*-------------------------- old-style bitmap header ------------------------

 typedef struct {
     USHORT    wType;
     ULONG     dwSize;
     int       xHotspot;
     int       yHotspot;
     ULONG     dwBitsOffset;
     USHORT    bmWidth;
     USHORT    bmHeight;
     USHORT    bmPlanes;
     USHORT    bmBitcount;
 } RCBITMAP;
 typedef RCBITMAP FAR *PRCBITMAP;


 /*--------------------------- Miscellaneous ---------------------------------

 ULONG        ulTerminateSem = 0;               /* main thread blocks while as
 HSEM        hsemTerminate  = &ulTerminateSem;

 ULONG        ulSzFmt   = 0;                       /* serializes access to spr
 HSEM        hsemSzFmt = &ulSzFmt;
 CHAR        szFmt[50];                       /* buffer used by sprintf()

 SWCNTRL swctl = { 0, 0, 0, 0, 0, SWL_VISIBLE, SWL_JUMPABLE, 0, 0 };
 HSWITCH hsw;                               /* handle to a switch list entry
 char        szTitle[80];                       /* Title bar text

 BOOL        fErrMem = FALSE;               /* set if alloc async stack fails


 /*------------------------- Function Prototypes -----------------------------

 VOID         CalcBounds( VOID);
 VOID         CalcTransform( HWND);
 MRESULT CALLBACK ClientWndProc( HWND, USHORT, MPARAM, MPARAM);
 BOOL         CreateBitmapHdcHps( HDC, HPS);
 BOOL         CreateThread( VOID);
 BOOL         CreatePicture( VOID);
 VOID         DestroyThread( VOID);
 BOOL         DoDraw( HRGN);
 VOID         DoHorzScroll( VOID);
 VOID         DoVertScroll( VOID);
 BOOL         DumpPicture( VOID);
 VOID         Finalize( VOID);
 BOOL         Initialize( VOID);
 VOID         Jumble( VOID);
 VOID         LeftDown( MPARAM);
 VOID         LeftUp( VOID);
 VOID         Load( PLOADINFO);
 VOID cdecl main( VOID);
 VOID         MessageInt( HWND, INT, PCH);
 VOID         MouseMove( MPARAM);
 VOID         MyMessageBox( HWND, PSZ);
 VOID FAR NewThread( VOID);
 BOOL         PrepareBitmap( VOID);
 BOOL         ReadBitmap( HFILE);
 VOID         Redraw( VOID);
 VOID         ReportError( HAB);
 BOOL         SegListCheck( INT);
 PSEGLIST SegListGet( LONG);
 BOOL         SegListUpdate( USHORT, PSEGLIST);
 BOOL         SendCommand( USHORT, ULONG);
 VOID         SetDVTransform( FIXED, FIXED, FIXED, FIXED, LONG, LONG, LONG);
 VOID         SetRect( PSEGLIST);
 VOID         ToggleFastDrag( VOID);
 VOID         Translate( PSEGLIST, PPOINTL);
 MRESULT  WndProcCommand( HWND, USHORT, MPARAM, MPARAM);
 MRESULT  WndProcCreate( HWND);
 MRESULT  WndProcPaint( VOID);
 MRESULT  WndProcSize( MPARAM, MPARAM);
 VOID         Zoom( SHORT);
 VOID         ZoomMenuItems( VOID);


 /****************************************************************************
 /*
 /*  MyMessageBox
 /*
 /*  Displays a message box with the given string.  To simplify matters,
 /*  the box will always have the same title ("Jigsaw"), will always
 /*  have a single button ("Ok"), will always have an exclamation point
 /*  icon, and will always be application modal.
 /*
 /****************************************************************************
 VOID
 MyMessageBox( hWnd, psz)

 HWND  hWnd;
 PSZ   psz;
 {
     WinMessageBox( HWND_DESKTOP
                  , hWnd
                  , psz
                  , szTitle
                  , 0
                  , MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL );
 }

 /****************************************************************************
 /*
 /* Main thread will initialize the process for PM services and process
 /* the application message queue until a WM_QUIT message is received. It will
 /* then destroy all PM resources and terminate. Any error during
 /* initialization will be reported and the process terminated.
 /*
 /****************************************************************************
 VOID cdecl
 main()
 {
   QMSG        qmsg;

   if( Initialize())
       while( WinGetMsg( habMain, &qmsg, NULL, 0, 0))
           WinDispatchMsg( habMain, &qmsg);
   else
       ReportError( habMain);
   Finalize();
 }


 /****************************************************************************
 /*
 /* The Initialize function will initialize the PM interface,
 /* create an application message queue, a standard frame window and a new
 /* thread to control drawing operations.  It will also initialize static
 /* strings.
 /*
 /****************************************************************************
 BOOL
 Initialize()
 {
   ULONG   flCreate;
   PID          pid;
   TID          tid;

   WinShowPointer( HWND_DESKTOP, TRUE);
   habMain = WinInitialize( 0);
   if( !habMain)
       return( FALSE);

   hmqMain = WinCreateMsgQueue( habMain,0);
   if( !hmqMain)
       return( FALSE);

   WinLoadString( habMain, (HMODULE) NULL, TITLEBAR, sizeof(szTitle), szTitle)
   if( !WinRegisterClass( habMain
                        , (PCH)szTitle
                        , (PFNWP)ClientWndProc
                        , CS_SIZEREDRAW
                        , 0 ))
       return( FALSE);

   flCreate =   (FCF_STANDARD | FCF_VERTSCROLL | FCF_HORZSCROLL)
              & ~(ULONG)FCF_TASKLIST;
   hwndFrame = WinCreateStdWindow( HWND_DESKTOP
                                 , WS_VISIBLE
                                 , &flCreate
                                 , szTitle
                                 , szTitle
                                 , WS_VISIBLE
                                 , (HMODULE) NULL
                                 , APPMENU
                                 , &hwndClient);
   if( !hwndFrame)
       return( FALSE);

   WinQueryWindowProcess( hwndFrame, &pid, &tid);
   swctl.hwnd          = hwndFrame;
   swctl.idProcess = pid;
   lstrcpy( swctl.szSwtitle, szTitle);
   hsw = WinAddSwitchEntry( &swctl);

   if( !CreateThread())                      /* create async thread
       return ( FALSE);
   if( !CreateBitmapHdcHps( &hdcBitmapFile, &hpsBitmapFile))
       return( FALSE);
   if( !CreateBitmapHdcHps( &hdcBitmapTemp, &hpsBitmapTemp))
       return( FALSE);
   if( !CreateBitmapHdcHps( &hdcBitmapDrag, &hpsBitmapDrag))
       return( FALSE);

   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Finalize will destroy the asynchronous drawing thread, all Presentation
 /* Manager resources, and terminate the process.
 /*
 /****************************************************************************
 VOID
 Finalize()
 {
   DestroyThread();

   while( pslHead != NULL )
       SegListUpdate( DEL_SEG, pslHead);
   if( hrgnInvalid)
       GpiDestroyRegion( hpsClient, hrgnInvalid);
   if( hpsClient)
       GpiAssociate( hpsClient, NULL);
   if( hpsPaint)
       GpiAssociate( hpsPaint, NULL);
   if( hpsBitmapFile)
       GpiAssociate( hpsBitmapFile, NULL);
   if( hpsBitmapTemp)
       GpiAssociate( hpsBitmapTemp, NULL);
   if( hpsBitmapDrag)
       GpiAssociate( hpsBitmapDrag, NULL);
   if( hwndFrame)
       WinDestroyWindow( hwndFrame);
   if( hmqMain)
       WinDestroyMsgQueue( hmqMain);
   if( habMain)
       WinTerminate( habMain);

   DosExit( EXIT_PROCESS, 0);
 }


 /****************************************************************************
 /*
 /* ReportError        will display the latest error information for the requi
 /* thread. No resources to be loaded if out of memory error.
 /*
 /****************************************************************************
 VOID
 ReportError( hab)

 HAB hab;
 {
   PERRINFO  perriBlk;
   PSZ            pszErrMsg;
   USHORT *  TempPtr;

   if( !hwndFrame)
       return;
   if( !fErrMem)
   {
       perriBlk = WinGetErrorInfo(hab);
       if( !perriBlk)
           return;
       SELECTOROF( pszErrMsg) = SELECTOROF(perriBlk);
       SELECTOROF( TempPtr)   = SELECTOROF(perriBlk);
       OFFSETOF( TempPtr)     = perriBlk->offaoffszMsg;
       OFFSETOF( pszErrMsg)   = *TempPtr;
       WinMessageBox( HWND_DESKTOP
                    , hwndFrame
                    , pszErrMsg
                    , szTitle
                    , 0
                    , MB_CUACRITICAL | MB_ENTER);
       WinFreeErrorInfo( perriBlk);
   } else
       WinMessageBox( HWND_DESKTOP
                    , hwndFrame
                    , "ERROR - Out Of Memory"
                    , szTitle
                    , 0
                    , MB_CUACRITICAL | MB_ENTER);
 }


 /****************************************************************************
 /*
 /* CreateThread  creates the asynchronous drawing thread. It will allocate
 /* stack space and create the thread.
 /*
 /****************************************************************************
 BOOL
 CreateThread()
 {
   PBYTE pbAsyncStack;                      /* long pointer to stack for new t


   if( DosAllocSeg( STACKSIZE, (PSEL)&selStack, 0 ))
   {
       fErrMem = TRUE;
       return( FALSE);
   }
   OFFSETOF(pbAsyncStack) = STACKSIZE-2;
   SELECTOROF(pbAsyncStack) = selStack;
   if( DosCreateThread( (PFNTHREAD)NewThread, &tidAsync, pbAsyncStack ))
       return( FALSE);
   return( TRUE);
 }


 /****************************************************************************
 /*
 /* DestroyThread  will send a message  to the asynchronous drawing thread
 /* commanding it to terminate itself. If the send is successful it will wait
 /* until the async thread has terminated. It will then release any stack spac
 /* used by that thread.
 /*
 /****************************************************************************
 VOID
 DestroyThread()
 {
   if( tidAsync)
   {
       DosSemSet( hsemTerminate);
       if( SendCommand( (USHORT)UM_DIE, (ULONG)NULL))
           DosSemWait( hsemTerminate, SEM_INDEFINITE_WAIT);
   }
   if( selStack)
       DosFreeSeg( selStack);
 }


 /****************************************************************************
 /*
 /* SendCommand        will attempt to post the required command and parameter
 /* the asynchronous drawing thread's message queue. The command will only
 /* be posted if the queue exists.
 /*
 /****************************************************************************
 BOOL
 SendCommand( usCommand, ulInfo)

 USHORT        usCommand;
 ULONG        ulInfo;
 {
   if( !hmqAsync)
       return( FALSE);

   switch( usCommand)
   {
     case UM_DIE:
     case UM_LEFTDOWN:
     case UM_LEFTUP:
     case UM_MOUSEMOVE:
     case UM_DRAW:
     case UM_HSCROLL:
     case UM_VSCROLL:
     case UM_ZOOM_IN:
     case UM_ZOOM_OUT:
     case UM_REDRAW:
     case UM_SIZING:
     case UM_FASTDRAG:
     case UM_JUMBLE:
     case UM_LOAD:

         return( WinPostQueueMsg( hmqAsync
                                , usCommand
                                , MPFROMLONG( ulInfo)
                                , MPFROMLONG( NULL  ) ) );
         break;

     default:
         return( TRUE);
   }
 }


 /****************************************************************************
 /*
 /* ClientWndProd is the window procedure associated with the client window.
 /*
 /****************************************************************************
 MRESULT CALLBACK
 ClientWndProc( hwnd, msg, mp1, mp2)

 HWND        hwnd;
 USHORT  msg;
 MPARAM        mp1;
 MPARAM        mp2;
 {
   CHAR  szTemp[128];

   switch( msg)
   {
     case WM_CREATE:
       return( WndProcCreate( hwnd));
       break;

     case WM_CLOSE:
       WinLoadString( habMain, (HMODULE) NULL, TERMINATE, sizeof(szTemp), (PSZ
       if( WinMessageBox( HWND_DESKTOP
                        , hwndFrame
                        , szTemp
                        , szTitle
                        , 0
                        , MB_CUAWARNING | MB_YESNO | MB_DEFBUTTON2)
                == MBID_YES)
           WinPostMsg( hwnd, WM_QUIT, NULL, NULL);
       break;

     case WM_PAINT:
       return( WndProcPaint());
       break;

     /************************************************************************
     /*
     /************************************************************************
     case WM_ERASEBACKGROUND:
       WinFillRect( (HPS)mp1, (PRECTL)mp2, CLR_BACKGROUND);
       return( FALSE);
       break;

     /************************************************************************
     /*
     /************************************************************************
     case WM_MINMAXFRAME:
       if( (((PSWP)mp1)->fs & SWP_RESTORE)  ||
           (((PSWP)mp1)->fs & SWP_MAXIMIZE) )
         SendCommand( (USHORT)UM_SIZING, 0L);
       break;

     /************************************************************************
     /* Process menu item commands, and commands generated from the keyboard
     /* via the accelerator table. Most are handled by the async thread
     /************************************************************************
     case WM_COMMAND:
       return( WndProcCommand( hwnd, msg, mp1, mp2));
       break;

     /************************************************************************
     /* Scrolling is handled by the async drawing thread. Simply pass on the
     /* command and parameters
     /************************************************************************
     case WM_HSCROLL:
       SendCommand( (USHORT)UM_HSCROLL, LONGFROMMP(mp2));
       break;

     case WM_VSCROLL:
       SendCommand( (USHORT)UM_VSCROLL, LONGFROMMP(mp2));
       break;

     /************************************************************************
     /* The client area is being resized.                                    *
     /************************************************************************
     case WM_SIZE:
       return( WndProcSize( mp1, mp2));
       break;

     /************************************************************************
     /* Mouse commands are handled by the async thread. Simply send on the
     /* command and parameters.
     /************************************************************************
     case WM_BUTTON1DBLCLK:
     case WM_BUTTON1DOWN:
       if( hwnd != WinQueryFocus( HWND_DESKTOP, FALSE))
           WinSetFocus( HWND_DESKTOP, hwnd);
       if( !fButtonDown)
       {
           fButtonDown = TRUE;
           SendCommand( (USHORT)UM_LEFTDOWN, LONGFROMMP(mp1));
       }
       return((MRESULT) TRUE);
       break;

     case WM_BUTTON1UP:
       if( !fButtonDown)
           return((MRESULT) TRUE);
       if( SendCommand( (USHORT)UM_LEFTUP, LONGFROMMP(mp1)))
           fButtonDown = FALSE;
       else
           WinAlarm( HWND_DESKTOP, WA_WARNING);
       return((MRESULT) TRUE);
       break;

     case WM_MOUSEMOVE:
       if( fButtonDown && (pslPicked != NULL))
           SendCommand( (USHORT)UM_MOUSEMOVE, LONGFROMMP(mp1));
       return( WinDefWindowProc( hwnd, msg, mp1, mp2));
       break;

     /************************************************************************
     /* Default for the rest
     /************************************************************************
     default:
       return( WinDefWindowProc( hwnd, msg, mp1, mp2));
   }

   return( FALSE);
 }

 /****************************************************************************
 /*
 /* Get the maximum client area size.  Create a window DC for the client
 /* area and a normal GPI Presentation Space and associate the two.  The GPI
 /* PS will be the maximum client area size and be in pels.
 /*
 /****************************************************************************
 MRESULT
 WndProcCreate( hwnd)

 HWND  hwnd;
 {
   SIZEL sizlPickApp;                      /* pick aperture size

   sizlMaxClient.cx = WinQuerySysValue( HWND_DESKTOP, SV_CXFULLSCREEN);
   sizlMaxClient.cy = WinQuerySysValue( HWND_DESKTOP, SV_CYFULLSCREEN);

   hdcClient = WinOpenWindowDC( hwnd);
   hpsClient = GpiCreatePS( habMain
                          , hdcClient
                          , &sizlMaxClient
                          , GPIA_ASSOC | PU_PELS );
   if( !hpsClient)
       return((MRESULT) TRUE);
   GpiSetAttrMode( hpsClient, AM_PRESERVE);

   hwndHorzScroll = WinWindowFromID( WinQueryWindow( hwnd, QW_PARENT, FALSE)
                                   , FID_HORZSCROLL);

   hwndVertScroll = WinWindowFromID( WinQueryWindow( hwnd, QW_PARENT, FALSE)
                                   , FID_VERTSCROLL);

   hpsPaint = GpiCreatePS( habMain, NULL, &sizlMaxClient, PU_PELS);

   hrgnInvalid = GpiCreateRegion( hpsClient, 0L, NULL);

   sizlPickApp.cx = sizlPickApp.cy = 1;
   GpiSetPickApertureSize( hpsClient, PICKAP_REC, &sizlPickApp);
   return( FALSE);
 }


 /****************************************************************************
 /*
 /* WM_PAINT message
 /*
 /****************************************************************************
 MRESULT
 WndProcPaint()

 {
   HRGN         hrgnUpdt;
   SHORT  sRgnType;

   hrgnUpdt = GpiCreateRegion( hpsPaint, 0L, NULL);
   sRgnType = WinQueryUpdateRegion( hwndClient, hrgnUpdt);
   WinValidateRegion( hwndClient, hrgnUpdt, FALSE);
   SendCommand( UM_DRAW, (ULONG)hrgnUpdt);
   return( FALSE);
 }

 /****************************************************************************
 /* Process menu item commands, and commands generated from the keyboard via
 /* the accelerator table.  Most are handled by the async thread
 /****************************************************************************
 MRESULT
 WndProcCommand( hwnd, msg, mp1, mp2)

 HWND        hwnd;
 USHORT  msg;
 MPARAM        mp1, mp2;
 {
   CHAR            szTemp[128];
   DLF            dlf;
   SEL            sel;
   PLOADINFO pli;
   PSZ            pszError, psz1, psz2;

   switch( SHORT1FROMMP(mp1))
   {
     case MENU_JUMBLE:
         SendCommand( UM_JUMBLE, 0L);
         break;

     case MENU_LOAD:
         DosAllocSeg( sizeof( LOADINFO), &sel, 0);
         pli = MAKEP( sel, 0);

         dlf.rgbAction            = DLG_OPENDLG;
         dlf.rgbFlags            = ATTRDIRLIST;
         dlf.phFile            = &(pli->hf);
         dlf.pszExt            = (PSZ)"\\*.bmp";
         dlf.pszAppName            = szTitle;
         dlf.pszTitle            = "Load Bitmap";
         dlf.pszInstructions = NULL;
         dlf.szFileName[0]   = '\0';
         dlf.szOpenFile[0]   = '\0';
         pszError            = "Error reading file.";

         switch( DlgFile( hwnd, &dlf))
         {
           case TDF_ERRMEM:
           case TDF_INVALID:
               MyMessageBox( hwnd, pszError);
               break;

           case TDF_NOOPEN:
               break;

           default:
               for( psz1 = dlf.szFileName, psz2 = pli->szFileName
                  ; *psz2++ = *psz1++
                  ; )
                   ;
               SendCommand( UM_LOAD, (LONG)pli);
               break;
         }
         break;
     /**********************************************************************/
     /* EXIT command, menu item or F3 key pressed. Give the operator a
     /* second chance, if confirmed post a WM_QUIT msg to the application  */
     /* msg queue. This will force the MAIN thread to terminate.           */
     /**********************************************************************/
     case MENU_EXIT:
       WinLoadString( habMain, (HMODULE) NULL, TERMINATE, sizeof(szTemp), szTe
       if( WinMessageBox( HWND_DESKTOP
                        , hwndFrame
                        , szTemp
                        , szTitle
                        , 0
                        , MB_CUAWARNING | MB_YESNO | MB_DEFBUTTON2)
             == MBID_YES)
         WinPostMsg( hwnd, WM_QUIT, NULL, NULL);
       break;

     /**********************************************************************/
     /* Pass on the rest to the async thread.
     /**********************************************************************/
     case MENU_ZOOMIN:
       SendCommand( UM_ZOOM_IN, 0L);
       break;

     case MENU_ZOOMOUT:
       SendCommand( UM_ZOOM_OUT, 0L);
       break;

     case MENU_FASTDRAG:
       SendCommand( UM_FASTDRAG, 0L);
       break;

     /**********************************************************************/
     /* Unrecognised => default
     /**********************************************************************/
     default:
       return( WinDefWindowProc(hwnd, msg, mp1, mp2));
   }
   return( FALSE);
 }

 /****************************************************************************
 /* Load a bitmap
 /****************************************************************************
 VOID
 Load( pli)

 PLOADINFO  pli;
 {
     PSZ     pszError;
     RECTL   rclClient;

     pszError = (PSZ)"Error reading file.";

     DumpPicture();
     if( !ReadBitmap( pli->hf) )
     {
       MyMessageBox( hwndClient, pszError);
       return;
     }
     if( !PrepareBitmap() )
     {
       MyMessageBox( hwndClient, pszError);
       return;
     }

     lstrcpy( swctl.szSwtitle, szTitle);
     lstrcat( swctl.szSwtitle, ": ");
     lstrcat( swctl.szSwtitle, pli->szFileName);
     WinChangeSwitchEntry( hsw, &swctl);
     WinSetWindowText( hwndFrame, swctl.szSwtitle);

     CreatePicture();
     lScale = 0;

     WinQueryWindowRect( hwndClient, &rclClient);
     ptsScrollMax.x = (SHORT)(rclClient.xRight - rclClient.xLeft);
     ptsHalfScrollMax.x = ptsScrollMax.x >> 1;
     ptsScrollPos.x = ptsHalfScrollMax.x;
     ptsOldScrollPos.x = ptsHalfScrollMax.x;
     WinSendMsg( hwndHorzScroll
               , SBM_SETSCROLLBAR
               , MPFROMSHORT( ptsScrollPos.x)
               , MPFROM2SHORT( 1, ptsScrollMax.x) );
     ptsScrollMax.y = (SHORT)(rclClient.yTop - rclClient.yBottom);
     ptsHalfScrollMax.y = ptsScrollMax.y >> 1;
     ptsScrollPos.y = ptsHalfScrollMax.y;
     ptsOldScrollPos.y = ptsHalfScrollMax.y;
     WinSendMsg( hwndVertScroll
               , SBM_SETSCROLLBAR
               , MPFROMSHORT( ptsScrollPos.y)
               , MPFROM2SHORT( 1, ptsScrollMax.y) );

     CalcBounds();
     CalcTransform( hwndClient);
     DosFreeSeg( SELECTOROF( pli));
 }
 /****************************************************************************
 /* Throw the pieces around the screen.
 /****************************************************************************
 VOID
 Jumble()
 {
   LONG            lWidth, lHeight;
   DATETIME  date;
   POINTL    ptl;
   RECTL     rclClient;
   PSEGLIST  psl;

   if( WinQueryWindowRect( hwndClient, &rclClient) )
   {
     lWidth  = rclClient.xRight - rclClient.xLeft;
     lHeight = rclClient.yTop   - rclClient.yBottom;
     if( (lWidth > 0) && (lHeight > 0) )
     {
       DosGetDateTime( &date);
       srand( (USHORT)date.hundredths);
       for( psl = pslHead; psl != NULL; psl = psl->pslNext)
       {
         ptl.x = rclClient.xLeft   + (rand() % lWidth);
         ptl.y = rclClient.yBottom + (rand() % lHeight);
         Translate( psl, &ptl);
         SetRect( psl);
       }
     }
   }
 }

 /****************************************************************************
 /* The client area is being resized.  The current scroll bar thumb position
 /* and scroll bar range must be recalculated prior to recalculating the
 /* default viewing transform for the picture.  Wait for subsequent WM_PAINT
 /* to do any drawing.
 /****************************************************************************
 MRESULT
 WndProcSize( mp1, mp2)

 MPARAM        mp1, mp2;
 {
   HWND        hwndFrameTemp;

   if( hwndFrame)
     hwndFrameTemp = hwndFrame;
   else
     hwndFrameTemp = WinQueryWindow( hwndClient, QW_PARENT, FALSE);

   ptsScrollMax.y = SHORT2FROMMP( mp2);
   ptsHalfScrollMax.y = ptsScrollMax.y >> 1;
   if( mp1)
   {
       ptsScrollPos.y = (SHORT)(((LONG)ptsScrollPos.y * (LONG)SHORT2FROMMP(mp2
       ptsOldScrollPos.y = (SHORT)(((LONG)ptsOldScrollPos.y * (LONG)SHORT2FROM
   } else
   {
       ptsScrollPos.y = ptsHalfScrollMax.y;     /* first sizing after window c
       ptsOldScrollPos.y = ptsHalfScrollMax.y;
   }
   WinSendMsg( hwndVertScroll
             , SBM_SETSCROLLBAR
             , MPFROMSHORT( ptsScrollPos.y)
             , MPFROM2SHORT( 1, ptsScrollMax.y) );


   ptsScrollMax.x = SHORT1FROMMP( mp2);
   ptsHalfScrollMax.x = ptsScrollMax.x >> 1;
   if( mp1)
   {
       ptsScrollPos.x = (SHORT)(((LONG)ptsScrollPos.x * (LONG)SHORT1FROMMP(mp2
       ptsOldScrollPos.x = (SHORT)(((LONG)ptsOldScrollPos.x * (LONG)SHORT1FROM
   } else
   {
       ptsScrollPos.x = ptsHalfScrollMax.x;     /* first sizing after window c
       ptsOldScrollPos.x = ptsHalfScrollMax.x;
   }
   WinSendMsg( hwndHorzScroll
             , SBM_SETSCROLLBAR
             , MPFROMSHORT( ptsScrollPos.x)
             , MPFROM2SHORT( 1, ptsScrollMax.x) );


   SendCommand( UM_SIZING, 0L);
   return( FALSE);
 }

 /****************************************************************************
 /*
 /* NewThread is the asynchronous drawing thread. It is responsible for all
 /* drawing.  It will initialize its PM interface and create an application
 /* message queue.  It will then monitor its message queue and process any
 /* commands received.
 /*
 /****************************************************************************
 VOID FAR
 NewThread()
 {
   QMSG          qmsgAsync, qmsgPeek;
   BOOL          fDone;

   /**************************************************************************
   /* Initialize the PM interface.  If it fails, terminate both threads.
   /**************************************************************************
   habAsync = WinInitialize( 0);
   if( !habAsync)
   {
       WinPostMsg( hwndClient, WM_QUIT, NULL, NULL);
       DosExit( EXIT_THREAD, 0);
   }

   /**************************************************************************
   /* Create a message queue.  If it fails, terminate both threads.
   /**************************************************************************
   hmqAsync = WinCreateMsgQueue( habAsync, 80);
   if( !hmqAsync)
   {
       WinPostMsg( hwndClient, WM_QUIT, NULL, NULL);
       WinTerminate( habAsync);
       DosExit( EXIT_THREAD, 0);
   }

   DosSetPrty( PRTYS_THREAD, PRTYC_NOCHANGE, sPrty, (TID)NULL);


   while( TRUE)
   {
     WinGetMsg( habAsync, &qmsgAsync, NULL, 0, 0);

     /************************************************************************
     /* process the commands
     /************************************************************************
     switch( qmsgAsync.msg)
     {

       /**********************************************************************
       /**********************************************************************
       case UM_LOAD:
         Load( (PLOADINFO)qmsgAsync.mp1);
         Redraw();
         break;

       /**********************************************************************
       case UM_JUMBLE:
         Jumble();
         Redraw();
         break;

       /**********************************************************************
       case UM_REDRAW:
         Redraw();
         break;

       /**********************************************************************
       /* DRAW will use the passed region containing the invalidated area of
       /* the screen, repaint it and then destroy the region.
       /**********************************************************************
       case UM_DRAW:
         DoDraw( (HRGN)qmsgAsync.mp1);
         if( qmsgAsync.mp1)
             GpiDestroyRegion( hpsClient, (HRGN)qmsgAsync.mp1);
         break;


       /**********************************************************************
       /* Get new scroll posn from command ( i.e. +/-1 +/-page) or new
       /* absolute position from parameter, update scroll posn, change the
       /* transform and update the thumb posn.  Finally update the window.
       /**********************************************************************
       case UM_HSCROLL:
         switch( SHORT2FROMMP( qmsgAsync.mp1) )
         {
             case SB_LINEUP:
                 ptsScrollPos.x -= ptsScrollLine.x;
                 break;
             case SB_LINEDOWN:
                 ptsScrollPos.x += ptsScrollLine.x;
                 break;
             case SB_SLIDERTRACK:
             case SB_SLIDERPOSITION:
                 for( fDone = FALSE; !fDone ;)
                 {
                   if( WinPeekMsg( habAsync
                                 , &qmsgPeek
                                 , NULL
                                 , UM_HSCROLL
                                 , UM_HSCROLL
                                 , PM_NOREMOVE))
                       if(   (SHORT2FROMMP( qmsgPeek.mp1) == SB_SLIDERTRACK)
                           ||(SHORT2FROMMP( qmsgPeek.mp1) == SB_SLIDERPOSITION
                           WinPeekMsg( habAsync
                                     , &qmsgAsync
                                     , NULL
                                     , UM_HSCROLL
                                     , UM_HSCROLL
                                     , PM_REMOVE);
                       else
                           fDone = TRUE;
                   else
                       fDone = TRUE;
                 }
                 ptsScrollPos.x = SHORT1FROMMP( qmsgAsync.mp1);
                 break;
             case SB_PAGEUP:
                 ptsScrollPos.x -= ptsScrollPage.x;
                 break;
             case SB_PAGEDOWN:
                 ptsScrollPos.x += ptsScrollPage.x;
                 break;
             case SB_ENDSCROLL:
                 break;
             default:
                 break;
         }
         DoHorzScroll();
         break;

       case UM_VSCROLL:
         switch( SHORT2FROMMP( qmsgAsync.mp1) )
         {
             case SB_LINEUP:
                 ptsScrollPos.y -= ptsScrollLine.y;
                 break;
             case SB_LINEDOWN:
                 ptsScrollPos.y += ptsScrollLine.y;
                 break;
             case SB_SLIDERTRACK:
             case SB_SLIDERPOSITION:
                 for( fDone = FALSE; !fDone ;)
                 {
                   if( WinPeekMsg( habAsync
                                 , &qmsgPeek
                                 , NULL
                                 , UM_VSCROLL
                                 , UM_VSCROLL
                                 , PM_NOREMOVE))
                       if(   (SHORT2FROMMP( qmsgPeek.mp1) == SB_SLIDERTRACK)
                           ||(SHORT2FROMMP( qmsgPeek.mp1) == SB_SLIDERPOSITION
                           WinPeekMsg( habAsync
                                     , &qmsgAsync
                                     , NULL
                                     , UM_VSCROLL
                                     , UM_VSCROLL
                                     , PM_REMOVE);
                       else
                           fDone = TRUE;
                   else
                       fDone = TRUE;
                 }
                 ptsScrollPos.y = SHORT1FROMMP( qmsgAsync.mp1);
                 break;
             case SB_PAGEUP:
                 ptsScrollPos.y -= ptsScrollPage.y;
                 break;
             case SB_PAGEDOWN:
                 ptsScrollPos.y += ptsScrollPage.y;
                 break;
             case SB_ENDSCROLL:
                 break;
             default:
                 break;
         }
         DoVertScroll();
         break;

       /**********************************************************************
       /* recalc the picture transform
       /**********************************************************************
       case UM_SIZING:
         CalcBounds();
         CalcTransform( hwndClient);
         break;

       /**********************************************************************
       /* adjust zoom factor
       /**********************************************************************
       case UM_ZOOM_IN:
         Zoom( ZOOM_IN_ARG);
         break;

       case UM_ZOOM_OUT:
         Zoom( ZOOM_OUT_ARG);
         break;

       /**********************************************************************
       /* toggle fast-drag
       /**********************************************************************
       case UM_FASTDRAG:
         ToggleFastDrag();
         break;

       /**********************************************************************
       /* Button down will cause a correlate on the picture to test for a hit.
       /* Any selected segment will be highlighted and redrawn as dynamic.
       /**********************************************************************
       case UM_LEFTDOWN:
         LeftDown( qmsgAsync.mp1);
         break;

       /**********************************************************************
       /* if a segment is being dragged it will be redrawn in a new posn
       /**********************************************************************
       case UM_MOUSEMOVE:
         for( fDone = FALSE; !fDone ;)
         {
           if( WinPeekMsg( habAsync
                         , &qmsgPeek
                         , NULL
                         , UM_MOUSEMOVE
                         , UM_LEFTUP
                         , PM_NOREMOVE))
               if( qmsgPeek.msg == UM_MOUSEMOVE)
                   WinPeekMsg( habAsync
                             , &qmsgAsync
                             , NULL
                             , UM_MOUSEMOVE
                             , UM_MOUSEMOVE
                             , PM_REMOVE);
               else
                   fDone = TRUE;
           else
               fDone = TRUE;
         }
         MouseMove( qmsgAsync.mp1);
         break;

       /**********************************************************************
       /* if a segment is being dragged it will be redrawn as normal
       /**********************************************************************
       case UM_LEFTUP:
         LeftUp();
         break;

       /**********************************************************************
       /* destroy resources and terminate
       /**********************************************************************
       case UM_DIE:
         WinDestroyMsgQueue( hmqAsync);
         WinTerminate( habAsync);
         DosEnterCritSec();
         DosSemClear( hsemTerminate);
         DosExit( EXIT_THREAD, 0);
         break;

       /**********************************************************************
       /* finish flush of commands from queue
       /**********************************************************************
       case UM_FLUSH:
         break;

       default:
         break;
     }
   }
 }

 /****************************************************************************
 /* button down will cause one segment to be indicated and made dynamic
 /****************************************************************************
 VOID
 LeftDown( mp)

 MPARAM         mp;
 {
   HRGN            hrgnUpdt;
   LONG            alSegTag[HITS][DEPTH][2];
   POINTL    ptl, aptl[4];
   RECTL     rcl;
   MATRIXLF  matlf;
   LONG            lOffset;
   BYTE            bBuff[128];
   CHAR            pszMsg[40];
   PSZ            psz1, psz2;

   ptl.x = (LONG)(SHORT)SHORT1FROMMP( mp);
   ptl.y = (LONG)(SHORT)SHORT2FROMMP( mp);

   /**************************************************************************
   /**************************************************************************
   for( pslPicked = pslTail; pslPicked != NULL; pslPicked = pslPicked->pslPrev
   {
     rcl = pslPicked->rclCurrent;
     GpiConvert( hpsClient, CVTC_MODEL, CVTC_DEVICE, 2L, (PPOINTL)&rcl);
     rcl.xRight++;
     rcl.yTop++;
     if( WinPtInRect( habAsync, &rcl, &ptl))
     {
         LONG lRet;

         GpiSetEditMode( hpsClient, SEGEM_INSERT);
         GpiOpenSegment( hpsClient, pslPicked->lSegId);
         GpiSetElementPointerAtLabel( hpsClient, FILLPATH);
         GpiFillPath( hpsClient, 1L, 0L);
         GpiCloseSegment( hpsClient);
         lRet = GpiCorrelateSegment( hpsClient
                                   , pslPicked->lSegId
                                   , PICKSEL_VISIBLE
                                   , &ptl
                                   , HITS
                                   , DEPTH
                                   , (PLONG)alSegTag );
         GpiOpenSegment( hpsClient, pslPicked->lSegId);
         GpiSetElementPointerAtLabel( hpsClient, FILLPATH);
         GpiOffsetElementPointer( hpsClient, 1L);
         GpiDeleteElement( hpsClient);
         GpiCloseSegment( hpsClient);

         if( lRet > 0)
             break;
     }
   }
   if( pslPicked)
     lPickedSeg         = pslPicked->lSegId;
   else
   {
     fButtonDown = FALSE;
     return;
   }
   if( (lPickedSeg < 1) || (lPickedSeg > lLastSegId) )
   {
     DosSemRequest( hsemSzFmt, SEM_INDEFINITE_WAIT);
     sprintf( szFmt, "Segment id out of range: %x", lPickedSeg);
     for( psz1 = szFmt, psz2 = pszMsg; *psz2++ = *psz1++; )
         ;
     DosSemClear( hsemSzFmt);
     MyMessageBox( hwndClient, pszMsg);
     fButtonDown = FALSE;
     return;
   }

   /**************************************************************************
   hrgnUpdt = GpiCreateRegion( hpsClient, 1L, &rcl);
   GpiSetSegmentAttrs( hpsClient, lPickedSeg, ATTR_VISIBLE, ATTR_OFF);

   GpiQuerySegmentTransformMatrix( hpsClient
                                 , lPickedSeg
                                 , 9L
                                 , &matlf );
   GpiBeginPath( hpsClient, 1L);
   GpiCallSegmentMatrix( hpsClient
                       , lPickedSeg + CALLSEG_BASE
                       , 9L
                       , &matlf
                       , TRANSFORM_REPLACE );
   GpiEndPath( hpsClient);
   GpiSetClipPath( hpsClient, 1L, SCP_AND);
   DoDraw( hrgnUpdt);
   GpiSetClipPath( hpsClient, 0L, SCP_RESET);
   GpiDestroyRegion( hpsClient, hrgnUpdt);

   /**************************************************************************
   ptlOffset = ptlBotLeft;
   GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 1L, &ptlOffset);

   aptl[0].x = pslPicked->rclBitBlt.xLeft;
   aptl[0].y = pslPicked->rclBitBlt.yBottom;
   aptl[1].x = pslPicked->rclBitBlt.xRight;
   aptl[1].y = pslPicked->rclBitBlt.yTop;
   aptl[2] = aptl[0];
   aptl[3] = aptl[1];
   GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 2L, &aptl[2]);
   aptl[2].x -= ptlOffset.x;
   aptl[2].y -= ptlOffset.y;
   aptl[3].x -= ptlOffset.x - 1;
   aptl[3].y -= ptlOffset.y - 1;
   GpiSetEditMode( hpsClient, SEGEM_INSERT);

   for( lOffset = 0L; GpiGetData( hpsClient
                                , lPickedSeg
                                , &lOffset
                                , DFORM_NOCONV
                                , (LONG)sizeof( bBuff)
                                , bBuff) > 0; )
       ;

   GpiOpenSegment( hpsClient, lPickedSeg);
   GpiDeleteElementsBetweenLabels( hpsClient, BITBLT_TOP, BITBLT_BOTTOM);
   if( !fFastDrag)
       GpiWCBitBlt( hpsClient
                  , hbmBitmapDrag
                  , 4L
                  , aptl
                  , ROP_SRCCOPY
                  , BBO_IGNORE );
   GpiCloseSegment( hpsClient);

   for( lOffset = 0L; GpiGetData( hpsClient
                                , lPickedSeg
                                , &lOffset
                                , DFORM_NOCONV
                                , (LONG)sizeof( bBuff)
                                , bBuff) > 0; )
       ;

   /**************************************************************************
   GpiSetSegmentAttrs( hpsClient, lPickedSeg, ATTR_VISIBLE, ATTR_ON);
   GpiSetSegmentAttrs( hpsClient, lPickedSeg, ATTR_DYNAMIC, ATTR_ON);
   GpiSetDrawControl( hpsClient, DCTL_DYNAMIC, DCTL_ON);
   GpiDrawSegment( hpsClient, lPickedSeg);

   WinSetCapture( HWND_DESKTOP, hwndClient);
 }




 /****************************************************************************
 /*
 /* move the segment
 /*
 /****************************************************************************
 VOID
 MouseMove( mp)

 MPARAM         mp;
 {
   RECTL   rcl;
   POINTL  ptl, ptlModel;

   ptl.x = (LONG)(SHORT)SHORT1FROMMP( mp);
   ptl.y = (LONG)(SHORT)SHORT2FROMMP( mp);

   ptlModel = ptl;
   GpiConvert( hpsClient, CVTC_DEVICE, CVTC_MODEL, 1L, &ptlModel);
   ptlModel.x = 5 * (ptlModel.x / 5);
   ptlModel.y = 5 * (ptlModel.y / 5);
   if( (ptlModel.x == ptlOldMouse.x) && (ptlModel.y == ptlOldMouse.y))
     return;
   ptlOldMouse.x = ptlModel.x;
   ptlOldMouse.y = ptlModel.y;

   /**************************************************************************
   /* clip mouse coords to client window
   /**************************************************************************
   WinQueryWindowRect(hwndClient, &rcl);
   if (rcl.xLeft > ptl.x)
     ptl.x = rcl.xLeft;
   if (rcl.xRight <= ptl.x)
     ptl.x = rcl.xRight;
   if (rcl.yBottom > ptl.y)
     ptl.y = rcl.yBottom;
   if (rcl.yTop <= ptl.y)
     ptl.y = rcl.yTop;

   GpiRemoveDynamics( hpsClient, lPickedSeg, lPickedSeg);
   Translate( pslPicked, &ptl);
   GpiDrawDynamics( hpsClient);
 }


 /****************************************************************************
 /*
 /* The dragged segment is being unselected.  Return it to its normal state.
 /*
 /****************************************************************************
 VOID
 LeftUp()
 {
   SEGLIST    sl;
   POINTL     aptl[4];

   if( !lPickedSeg)
     return;
   GpiRemoveDynamics( hpsClient, lPickedSeg, lPickedSeg);
   GpiSetSegmentAttrs( hpsClient, lPickedSeg, ATTR_DYNAMIC, ATTR_OFF);

   /**************************************************************************
   ptlOffset = ptlBotLeft;
   GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 1L, &ptlOffset);

   aptl[0].x = pslPicked->rclBitBlt.xLeft;
   aptl[0].y = pslPicked->rclBitBlt.yBottom;
   aptl[1].x = pslPicked->rclBitBlt.xRight;
   aptl[1].y = pslPicked->rclBitBlt.yTop;
   aptl[2] = aptl[0];
   aptl[3] = aptl[1];
   GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 2L, &aptl[2]);
   aptl[2].x -= ptlOffset.x;
   aptl[2].y -= ptlOffset.y;
   aptl[3].x -= ptlOffset.x - 1;
   aptl[3].y -= ptlOffset.y - 1;
   GpiSetEditMode( hpsClient, SEGEM_INSERT);
   GpiOpenSegment( hpsClient, lPickedSeg);
   GpiDeleteElementsBetweenLabels( hpsClient, BITBLT_TOP, BITBLT_BOTTOM);
   GpiWCBitBlt( hpsClient
              , hbmBitmapTemp
              , 4L
              , aptl
              , ROP_SRCCOPY
              , BBO_IGNORE );
   GpiCloseSegment( hpsClient);

   /**************************************************************************
   GpiDrawSegment( hpsClient, lPickedSeg);
   GpiSetSegmentPriority( hpsClient, lPickedSeg, 0L, LOWER_PRI); /* highest
   SetRect( pslPicked);

   sl = *pslPicked;
   SegListUpdate( DEL_SEG, pslPicked);
   SegListUpdate( ADD_TAIL_SEG, &sl);            /* at tail => highest priorit
   pslPicked = NULL;

   WinSetCapture( HWND_DESKTOP, (HWND)NULL);
 }


 /****************************************************************************
 /*
 /* DoHorzScroll will horizontally scroll the current contents of
 /* the client area and redraw the invalidated area
 /*
 /****************************************************************************
 VOID
 DoHorzScroll()
 {
   RECTL     rcl;
   HRGN            hrgn;
   MATRIXLF  matlf;

   if( ptsScrollPos.x > ptsScrollMax.x)
       ptsScrollPos.x = ptsScrollMax.x;
   if( ptsScrollPos.x < 0)
       ptsScrollPos.x = 0;

   if( ptsOldScrollPos.x != ptsScrollPos.x)
       WinSendMsg( hwndHorzScroll
                 , SBM_SETPOS
                 , MPFROM2SHORT( ptsScrollPos.x, 0)
                 , MPFROMLONG( NULL));

   /**************************************************************************
   /* Scroll the window the reqd amount, using bitblt'ing (ScrollWindow)
   /* if any of the screen still in view, and paint into uncovered region;
   /* else repaint the whole client area.
   /**************************************************************************
   hrgn = GpiCreateRegion( hpsClient, 0L, NULL);
   if( abs( ptsScrollPos.x - ptsOldScrollPos.x) <= ptsScrollMax.x)
   {
       WinScrollWindow( hwndClient
                      , ptsOldScrollPos.x - ptsScrollPos.x
                      , 0
                      , NULL
                      , NULL
                      , hrgn
                      , &rcl
                      , 0);
   } else
   {
       WinQueryWindowRect( hwndClient, &rcl);
       GpiSetRegion( hpsClient, hrgn, 1L, &rcl);
   }
   GpiQueryDefaultViewMatrix( hpsClient, 9L, &matlf );
   matlf.lM31 -= ptsScrollPos.x - ptsOldScrollPos.x;
   GpiSetDefaultViewMatrix( hpsClient, 9L, &matlf, TRANSFORM_REPLACE);

   DoDraw( hrgn);
   ptsOldScrollPos.x = ptsScrollPos.x;
   GpiDestroyRegion( hpsClient, hrgn);
 }

 /****************************************************************************
 /*
 /* DoVertScroll will vertically scroll the current contents of
 /* the client area and redraw the invalidated area
 /*
 /****************************************************************************
 VOID
 DoVertScroll()
 {
   RECTL     rcl;
   HRGN            hrgn;
   MATRIXLF  matlf;

   if( ptsScrollPos.y > ptsScrollMax.y)
       ptsScrollPos.y = ptsScrollMax.y;
   if( ptsScrollPos.y < 0)
       ptsScrollPos.y = 0;

   if( ptsOldScrollPos.y != ptsScrollPos.y)
       WinSendMsg( hwndVertScroll
                 , SBM_SETPOS
                 , MPFROM2SHORT( ptsScrollPos.y, 0)
                 , MPFROMLONG( NULL));

   /**************************************************************************
   /* Scroll the window the reqd amount, using bitblt'ing (ScrollWindow)
   /* if any of the screen still in view, and paint into uncovered region;
   /* else repaint the whole client area.
   /**************************************************************************
   hrgn = GpiCreateRegion( hpsClient, 0L, NULL);
   if( abs( ptsScrollPos.y - ptsOldScrollPos.y) <= ptsScrollMax.y)
   {
       WinScrollWindow( hwndClient
                      , 0
                      , ptsScrollPos.y - ptsOldScrollPos.y
                      , NULL
                      , NULL
                      , hrgn
                      , &rcl
                      , 0);
   } else
   {
       WinQueryWindowRect( hwndClient, &rcl);
       GpiSetRegion( hpsClient, hrgn, 1L, &rcl);
   }
   GpiQueryDefaultViewMatrix( hpsClient, 9L, &matlf );
   matlf.lM32 += ptsScrollPos.y - ptsOldScrollPos.y;
   GpiSetDefaultViewMatrix( hpsClient, 9L, &matlf, TRANSFORM_REPLACE);

   DoDraw( hrgn);
   ptsOldScrollPos.y = ptsScrollPos.y;
   GpiDestroyRegion( hpsClient, hrgn);
 }

 /****************************************************************************
 /*
 /* Redraw the entire client window.
 /*
 /****************************************************************************
 VOID
 Redraw()
 {
   RECTL   rclInvalid;
   HRGN          hrgnUpdt;

   WinQueryWindowRect( hwndClient, &rclInvalid);
   hrgnUpdt = GpiCreateRegion( hpsClient, 1L, &rclInvalid);
   DoDraw( hrgnUpdt);
   GpiDestroyRegion( hpsClient, hrgnUpdt);
 }


 /****************************************************************************
 /*
 /* toggle the fast-drag flag and update the menu check-box
 /*
 /****************************************************************************
 VOID
 ToggleFastDrag()
 {
   MENUITEM mi;
   HWND           hwndMenu, hwndOptions;

   hwndMenu = WinWindowFromID( hwndFrame, FID_MENU);
   WinSendMsg( hwndMenu
             , MM_QUERYITEM
             , MPFROM2SHORT( SM_OPTIONS, FALSE)
             , MPFROMP( (PMENUITEM)&mi));
   hwndOptions = mi.hwndSubMenu;

   if( fFastDrag)
   {
     fFastDrag = FALSE;
     WinSendMsg( hwndOptions
               , MM_SETITEMATTR
               , MPFROM2SHORT( MENU_FASTDRAG, TRUE)
               , MPFROM2SHORT( MIA_CHECKED, ~MIA_CHECKED) );
   }
   else
   {
     fFastDrag = TRUE;
     WinSendMsg( hwndOptions
               , MM_SETITEMATTR
               , MPFROM2SHORT( MENU_FASTDRAG, TRUE)
               , MPFROM2SHORT( MIA_CHECKED, MIA_CHECKED) );
   }
 }

 /****************************************************************************
 /*
 /* adjust zoom factor and recalc the picture transform, then do a redraw of
 /* whole screen
 /*
 /****************************************************************************
 VOID
 Zoom( sInOrOut)

 SHORT sInOrOut;
 {
   LONG         lScaleOld;

   lScaleOld = lScale;
   lScale += sInOrOut;
   if( lScale > ZOOM_MAX)
     lScale = ZOOM_MAX;
   else
     if( lScale < -ZOOM_MAX)
       lScale = -ZOOM_MAX;
   if( lScale != lScaleOld)
   {
       ZoomMenuItems();
       CalcBounds();
       CalcTransform( hwndClient);
       Redraw();
   }
 }

 /****************************************************************************
 /*
 /* enable/disable zoom menu items depending on scaling
 /*
 /****************************************************************************
 VOID
 ZoomMenuItems()
 {
   MENUITEM  mi;
   HWND            hwndMenu, hwndOptions;

   hwndMenu = WinWindowFromID( hwndFrame, FID_MENU);
   WinSendMsg( hwndMenu
             , MM_QUERYITEM
             , MPFROM2SHORT( SM_OPTIONS, FALSE)
             , MPFROMP( (PMENUITEM)&mi));
   hwndOptions = mi.hwndSubMenu;

   if( lScale >= ZOOM_MAX)
   {
       WinSendMsg( hwndOptions
                 , MM_SETITEMATTR
                 , MPFROM2SHORT( MENU_ZOOMIN, TRUE)
                 , MPFROM2SHORT( MIA_DISABLED, MIA_DISABLED));
       WinSendMsg( hwndOptions
                 , MM_SETITEMATTR
                 , MPFROM2SHORT( MENU_ZOOMOUT, TRUE)
                 , MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED));
   } else
   {
       if( lScale <= - ZOOM_MAX)
       {
           WinSendMsg( hwndOptions
                     , MM_SETITEMATTR
                     , MPFROM2SHORT( MENU_ZOOMOUT, TRUE)
                     , MPFROM2SHORT( MIA_DISABLED, MIA_DISABLED));
           WinSendMsg( hwndOptions
                     , MM_SETITEMATTR
                     , MPFROM2SHORT( MENU_ZOOMIN, TRUE)
                     , MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED));
       } else
       {
           WinSendMsg( hwndOptions
                     , MM_SETITEMATTR
                     , MPFROM2SHORT( MENU_ZOOMOUT, TRUE)
                     , MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED));
           WinSendMsg( hwndOptions
                     , MM_SETITEMATTR
                     , MPFROM2SHORT( MENU_ZOOMIN, TRUE)
                     , MPFROM2SHORT( MIA_DISABLED, ~MIA_DISABLED));
       }
   }
 }

 /****************************************************************************
 /*
 /* Determine the bounding rect of a segment.
 /*
 /****************************************************************************
 VOID
 SetRect( psl)

 PSEGLIST  psl;
 {
   GpiResetBoundaryData( hpsClient);
   GpiSetDrawControl( hpsClient, DCTL_DISPLAY, DCTL_OFF);
   GpiSetDrawControl( hpsClient, DCTL_BOUNDARY, DCTL_ON);
   GpiDrawSegment( hpsClient, psl->lSegId);
   GpiSetDrawControl( hpsClient, DCTL_DISPLAY, DCTL_ON);
   GpiSetDrawControl( hpsClient, DCTL_BOUNDARY, DCTL_OFF);
   GpiQueryBoundaryData( hpsClient, &(psl->rclCurrent));
 }

 /****************************************************************************
 /*
 /* Translate a segment
 /*
 /****************************************************************************
 VOID
 Translate( psl, pptlNew)

 PSEGLIST  psl;
 PPOINTL   pptlNew;
 {
   POINTL    ptl;
   MATRIXLF  matlf;

   ptl = *pptlNew;
   GpiConvert( hpsClient, CVTC_DEVICE, CVTC_MODEL, 1L, &ptl);
   ptl.x = (ptl.x / 5) * 5;
   ptl.y = (ptl.y / 5) * 5;
   ptl.x -= 25;
   ptl.y -= 25;

   GpiQuerySegmentTransformMatrix( hpsClient
                                 , psl->lSegId
                                 , 9L
                                 , &matlf);
   matlf.lM31 = ptl.x - (psl->ptlLocation).x;
   matlf.lM32 = ptl.y - (psl->ptlLocation).y;
   GpiSetSegmentTransformMatrix( hpsClient
                               , psl->lSegId
                               , 9L
                               , &matlf
                               , TRANSFORM_REPLACE);
 }


 /****************************************************************************
 /*
 /* set the default viewing transform
 /*
 /****************************************************************************
 VOID
 SetDVTransform( fx11, fx12, fx21, fx22, l31, l32, lType)

 FIXED        fx11, fx12, fx21, fx22;
 LONG        l31, l32, lType;
 {
   MATRIXLF  matlf;

   matlf.fxM11 = fx11;
   matlf.fxM12 = fx12;
   matlf.lM13  = 0L;
   matlf.fxM21 = fx21;
   matlf.fxM22 = fx22;
   matlf.lM23  = 0L;
   matlf.lM31  = l31;
   matlf.lM32  = l32;
   matlf.lM33  = 1L;
   GpiSetDefaultViewMatrix( hpsClient, 9L, &matlf, lType);
 }

 /****************************************************************************
 /*
 /* get bounding rect of whole picture in model coordinates
 /*
 /****************************************************************************
 VOID
 CalcBounds()
 {
   PSEGLIST  psl;
   RECTL     rcl;

   if( !pslHead)
     return;
   rclBounds = pslHead->rclCurrent;
   for( psl = pslHead->pslNext; psl != NULL; psl = psl->pslNext)
   {
     rcl = psl->rclCurrent;
     if( rcl.xLeft < rclBounds.xLeft)
       rclBounds.xLeft = rcl.xLeft;
     if( rcl.xRight > rclBounds.xRight)
       rclBounds.xRight = rcl.xRight;
     if( rcl.yTop > rclBounds.yTop)
       rclBounds.yTop = rcl.yTop;
     if( rcl.yBottom < rclBounds.yBottom)
       rclBounds.yBottom = rcl.yBottom;
   }
 }

 /****************************************************************************
 /*
 /* Calculate and set the default viewing transform based on zoom and scroll
 /*
 /****************************************************************************
 VOID
 CalcTransform( hwnd)

 HWND hwnd;
 {
   RECTL     rclClient;
   POINTL    ptlCenter, ptlTrans, ptlScale, aptl[4];
   HRGN            hrgn;
   PSEGLIST  psl;

   /**************************************************************************
   /* from bounding rect of picture get center of picture
   /**************************************************************************
   ptlCenter.x = (rclBounds.xLeft   + rclBounds.xRight) / 2;
   ptlCenter.y = (rclBounds.yBottom + rclBounds.yTop  ) / 2;

   /**************************************************************************
   /* translate center of picture to origin
   /**************************************************************************
   SetDVTransform( (FIXED)UNITY
                 , (FIXED)0
                 , (FIXED)0
                 , (FIXED)UNITY
                 , -ptlCenter.x
                 , -ptlCenter.y
                 , TRANSFORM_REPLACE);

   /**************************************************************************
   /* scale down to 60% of max client area
   /**************************************************************************
   ptlScale.x = (6 * UNITY * sizlMaxClient.cx) /
                (10 * (ptlTopRight.x - ptlBotLeft.x));
   ptlScale.y = (6 * UNITY * sizlMaxClient.cy) /
                (10 * (ptlTopRight.y - ptlBotLeft.y));

   /**************************************************************************
   /* add in zoom scale
   /**************************************************************************
   ptlScale.x += ptlScale.x * lScale / (ZOOM_MAX + 1);
   ptlScale.y += ptlScale.y * lScale / (ZOOM_MAX + 1);

   SetDVTransform( (FIXED)ptlScale.x
                 , (FIXED)0
                 , (FIXED)0
                 , (FIXED)ptlScale.y
                 , 0L
                 , 0L
                 , TRANSFORM_ADD);

   /**************************************************************************
   /* translate center of picture to center of client window
   /**************************************************************************
   WinQueryWindowRect( hwnd, &rclClient);
   ptlTrans.x = (rclClient.xRight - rclClient.xLeft)   / 2;
   ptlTrans.y = (rclClient.yTop         - rclClient.yBottom) / 2;

   /**************************************************************************
   /* add in horizontal and vertical scrolling factors
   /**************************************************************************
   ptlTrans.x += ptsScrollPos.x - ptsHalfScrollMax.x;
   ptlTrans.y += ptsScrollPos.y - ptsHalfScrollMax.y;
   SetDVTransform( (FIXED)UNITY
                 , (FIXED)0
                 , (FIXED)0
                 , (FIXED)UNITY
                 , ptlTrans.x
                 , ptlTrans.y
                 , TRANSFORM_ADD);

   /**************************************************************************
   /* create a shadow bitmap of the original, sized to the current output size
   /**************************************************************************
   aptl[0] = ptlBotLeft;
   aptl[1] = ptlTopRight;
   GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 2L, aptl);
   ptlOffset = aptl[0];

   aptl[0].x -= ptlOffset.x;
   aptl[0].y -= ptlOffset.y;
   aptl[1].x -= ptlOffset.x - 1;
   aptl[1].y -= ptlOffset.y - 1;
   aptl[2].x = 0L;
   aptl[2].y = 0L;
   aptl[3].x = bmpBitmapFile.cx;
   aptl[3].y = bmpBitmapFile.cy;
   GpiSetBitmap( hpsBitmapTemp, hbmBitmapTemp);
   GpiBitBlt( hpsBitmapTemp
            , hpsBitmapFile
            , 4L
            , aptl
            , ROP_SRCCOPY
            , BBO_IGNORE);
   GpiSetBitmap( hpsBitmapTemp, NULL);

   /**************************************************************************
   /* create a copy of the shadow bitmap, adjusted to appear normal when
   /* bitblt'd in XOR mode onto a CLR_BACKGROUND background (dynamic segment)
   /**************************************************************************
   GpiSetBitmap( hpsBitmapDrag, hbmBitmapDrag);
   GpiSetColor( hpsBitmapDrag, CLR_BACKGROUND);
   hrgn = GpiCreateRegion( hpsBitmapDrag, 1L, (PRECTL)aptl);
   GpiPaintRegion( hpsBitmapDrag, hrgn);
   GpiDestroyRegion( hpsBitmapDrag, hrgn);
   GpiBitBlt( hpsBitmapDrag
            , hpsBitmapFile
            , 4L
            , aptl
            , ROP_SRCINVERT
            , BBO_IGNORE);
   GpiSetBitmap( hpsBitmapDrag, NULL);

   for( psl = pslHead; psl != NULL; psl = psl->pslNext)
   {
     aptl[0].x = psl->rclBitBlt.xLeft;
     aptl[0].y = psl->rclBitBlt.yBottom;
     aptl[1].x = psl->rclBitBlt.xRight;
     aptl[1].y = psl->rclBitBlt.yTop;
     aptl[2] = aptl[0];
     aptl[3] = aptl[1];
     GpiConvert( hpsClient, CVTC_WORLD, CVTC_DEVICE, 2L, &aptl[2]);
     aptl[2].x -= ptlOffset.x;
     aptl[2].y -= ptlOffset.y;
     aptl[3].x -= ptlOffset.x - 1;
     aptl[3].y -= ptlOffset.y - 1;
     GpiSetEditMode( hpsClient, SEGEM_INSERT);
     GpiOpenSegment( hpsClient, psl->lSegId);
     GpiDeleteElementsBetweenLabels( hpsClient, BITBLT_TOP, BITBLT_BOTTOM);
     GpiWCBitBlt( hpsClient
                , hbmBitmapTemp
                , 4L
                , aptl
                , ROP_SRCCOPY
                , BBO_IGNORE );
     GpiCloseSegment( hpsClient);
   }
 }


 /****************************************************************************
 /*
 /* Draw the picture, using the passed region for clipping.
 /* Test each segment to see if its bounding box intersects the bounding box
 /* of the clipping region.  Draw only if there is an intersection.
 /*
 /****************************************************************************
 BOOL
 DoDraw( hrgn)

 HRGN        hrgn;
 {
   HRGN            hrgnOld;
   RECTL     rcl, rclRegion, rclDst;
   PSEGLIST  psl;

   GpiSetColor( hpsClient, CLR_BACKGROUND);
   GpiPaintRegion( hpsClient, hrgn);

   GpiQueryRegionBox( hpsClient, hrgn, &rclRegion);
   GpiSetClipRegion( hpsClient, hrgn, &hrgnOld);
   for( psl = pslHead; psl != NULL; psl = psl->pslNext)
   {
     rcl = psl->rclCurrent;
     GpiConvert( hpsClient, CVTC_MODEL, CVTC_DEVICE, 2L, (PPOINTL)&rcl);
     rcl.xRight++;
     rcl.yTop++;
     if( WinIntersectRect( habAsync, &rclDst, &rcl, &rclRegion))
         GpiDrawSegment( hpsClient, psl->lSegId);
   }
   GpiSetClipRegion( hpsClient, NULL, &hrgnOld);

   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Return a pointer to a segment list member, based on segment id.
 /*
 /****************************************************************************
 PSEGLIST
 SegListGet( lSeg)

 LONG           lSeg;
 {
   PSEGLIST  psl;

   for( psl = pslHead; psl != NULL; psl = psl->pslNext)
     if( psl->lSegId == lSeg)
       return( psl);
   return( NULL);
 }

 /****************************************************************************
 /*
 /* Check the segment list for obvious errors.
 /*
 /****************************************************************************
 BOOL
 SegListCheck( iLoc)

 INT   iLoc;
 {
   PSEGLIST   psl;
   CHAR             pszMsg[50];
   PSZ             psz1, psz2;

   pszMsg[0] = '\0';
   for( psl = pslHead; psl != NULL; psl = psl->pslNext)
     if( (psl->lSegId < 1) || (psl->lSegId > lLastSegId) )
     {
       DosSemRequest( hsemSzFmt, SEM_INDEFINITE_WAIT);
       sprintf( szFmt, "Bad head segment list, location %d", iLoc);
       for( psz1 = szFmt, psz2 = pszMsg; *psz2++ = *psz1++; )
           ;
       DosSemClear( hsemSzFmt);
       MyMessageBox( hwndClient, pszMsg);
       return( FALSE);
     }
   for( psl = pslTail; psl != NULL; psl = psl->pslPrev)
     if( (psl->lSegId < 1) || (psl->lSegId > lLastSegId) )
     {
       DosSemRequest( hsemSzFmt, SEM_INDEFINITE_WAIT);
       sprintf( szFmt, "Bad head segment list, location %d", iLoc);
       for( psz1 = szFmt, psz2 = pszMsg; *psz2++ = *psz1++; )
           ;
       DosSemClear( hsemSzFmt);
       MyMessageBox( hwndClient, pszMsg);
       return( FALSE);
     }
   return( TRUE);
 }
 /****************************************************************************
 /*
 /* Add (at head or tail) or delete a specified segment list member.
 /*
 /****************************************************************************
 BOOL
 SegListUpdate( usOperation, pslUpdate)

 USHORT         usOperation;
 PSEGLIST pslUpdate;
 {
   PSEGLIST psl;
   SEL           sel;

   switch( usOperation)
   {
     case ADD_HEAD_SEG:
       DosAllocSeg( sizeof( SEGLIST), &sel, 0);
       if( pslHead == NULL)
       {
         pslHead = MAKEP( sel, 0);
         if( pslHead == NULL)
           return( FALSE);
         *pslHead = *pslUpdate;
         pslHead->pslPrev = NULL;
         pslHead->pslNext = NULL;
         pslTail = pslHead;
       } else
       {
         psl = MAKEP( sel, 0);
         if( psl == NULL)
           return( FALSE);
         *psl = *pslUpdate;
         pslHead->pslPrev = psl;
         psl->pslNext = pslHead;
         psl->pslPrev = NULL;
         pslHead = psl;
       }
       return( TRUE);
       break;

     case ADD_TAIL_SEG:
       DosAllocSeg( sizeof( SEGLIST), &sel, 0);
       if( pslTail == NULL)
       {
         pslHead = MAKEP( sel, 0);
         if( pslHead == NULL)
           return( FALSE);
         *pslHead = *pslUpdate;
         pslHead->pslPrev = NULL;
         pslHead->pslNext = NULL;
         pslTail = pslHead;
       } else
       {
         psl = MAKEP( sel, 0);
         if( psl == NULL)
           return( FALSE);
         *psl = *pslUpdate;
         pslTail->pslNext = psl;
         psl->pslPrev = pslTail;
         psl->pslNext = NULL;
         pslTail = psl;
       }
       return( TRUE);
       break;

     case DEL_SEG:
       for( psl = pslHead; psl != NULL; psl = psl->pslNext)
       {
         if( psl->lSegId == pslUpdate->lSegId)
         {
           if( psl == pslHead)
           {
             pslHead = psl->pslNext;
             if( pslHead == NULL)
               pslTail = NULL;
             else
               pslHead->pslPrev = NULL;
           }else if( psl == pslTail)
           {
             pslTail = psl->pslPrev;
             pslTail->pslNext = NULL;
           } else
           {
             (psl->pslPrev)->pslNext = psl->pslNext;
             (psl->pslNext)->pslPrev = psl->pslPrev;
           }
           DosFreeSeg( SELECTOROF(psl));
           return( TRUE);
           break;
         }
       }
       return( FALSE);
       break;

     default:
       return( FALSE);
   }
 }



 /****************************************************************************
 /*
 /* DumpPicture will free the list and segment store for the picture
 /*
 /****************************************************************************
 BOOL
 DumpPicture()
 {
   while( pslHead != NULL )
     SegListUpdate( DEL_SEG, pslHead);
   GpiDeleteSegments( hpsClient, 1L, CALLSEG_BASE + lLastSegId);
   GpiSetBitmap( hpsBitmapFile, NULL);
   if( hbmBitmapFile)
       GpiDeleteBitmap( hbmBitmapFile);
   GpiSetBitmap( hpsBitmapTemp, NULL);
   if( hbmBitmapTemp)
       GpiDeleteBitmap( hbmBitmapTemp);
   GpiSetBitmap( hpsBitmapDrag, NULL);
   if( hbmBitmapDrag)
       GpiDeleteBitmap( hbmBitmapDrag);

   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Draw the picture into segment store.
 /*
 /****************************************************************************
 BOOL
 CreatePicture()
 {

   POINTL    ptl, aptlSides[12], aptlControl[12];
   SEGLIST   sl;
   LONG            lCallSegId, l;

   /**************************************************************************
   /* reset the default viewing transform to identity
   /**************************************************************************
   SetDVTransform( (FIXED)UNITY
                 , (FIXED)0
                 , (FIXED)0
                 , (FIXED)UNITY
                 , 0L
                 , 0L
                 , TRANSFORM_REPLACE);

   /**************************************************************************
   /* set to store mode
   /**************************************************************************
   GpiSetDrawingMode( hpsClient, DM_RETAIN);

   /**************************************************************************
   /* chaining and detectability off, fastchaining off
   /**************************************************************************
   GpiSetInitialSegmentAttrs( hpsClient, ATTR_CHAINED, ATTR_OFF);
   GpiSetInitialSegmentAttrs( hpsClient, ATTR_DETECTABLE, ATTR_OFF);
   GpiSetInitialSegmentAttrs( hpsClient, ATTR_FASTCHAIN, ATTR_OFF);

   /**************************************************************************
   /* draw the pieces
   /**************************************************************************
   lLastSegId = 0;
   lCallSegId = CALLSEG_BASE;
   for( ptl.x = ptlBotLeft.x; ptl.x < ptlTopRight.x; ptl.x += 50)
   {
     for( ptl.y = ptlBotLeft.y; ptl.y < ptlTopRight.y; ptl.y += 50)
     {
       /**********************************************************************
       /* compute the piece outline control points
       /**********************************************************************
       aptlControl[0].x = 10L;
       aptlControl[0].y = 10L;
       aptlControl[1].x = 40L;
       aptlControl[1].y = -10L;
       aptlControl[2].x = 50L;
       aptlControl[2].y = 0L;

       aptlControl[3].x = 40L;
       aptlControl[3].y = 10L;
       aptlControl[4].x = 60L;
       aptlControl[4].y = 40L;
       aptlControl[5].x = 50L;
       aptlControl[5].y = 50L;

       aptlControl[6].x = 40L;
       aptlControl[6].y = 40L;
       aptlControl[7].x = 10L;
       aptlControl[7].y = 60L;
       aptlControl[8].x = 0L;
       aptlControl[8].y = 50L;

       aptlControl[9].x        = 10L;
       aptlControl[9].y        = 40L;
       aptlControl[10].x = -10L;
       aptlControl[10].y = 10L;
       aptlControl[11].x = 0L;
       aptlControl[11].y = 0L;

       if( ptl.y == ptlBotLeft.y)
       {
         aptlControl[0].y = 0L;
         aptlControl[1].y = 0L;
       }

       if( (ptl.x + 50) == ptlTopRight.x)
       {
         aptlControl[3].x = 50L;
         aptlControl[4].x = 50L;
       }

       if( (ptl.y + 50) == ptlTopRight.y)
       {
         aptlControl[6].y = 50L;
         aptlControl[7].y = 50L;
       }

       if( ptl.x == ptlBotLeft.x)
       {
         aptlControl[ 9].x = 0L;
         aptlControl[10].x = 0L;
       }

       for( l=0; l<12; l++)
       {
         aptlSides[l].x = ptl.x + aptlControl[l].x;
         aptlSides[l].y = ptl.y + aptlControl[l].y;
       }

       GpiOpenSegment( hpsClient, ++lCallSegId);
       GpiMove( hpsClient, &ptl);
       GpiPolyLine( hpsClient, 12L, aptlSides);
       GpiCloseSegment( hpsClient);

       /**********************************************************************
       /* draw the root segment
       /**********************************************************************
       GpiOpenSegment( hpsClient, ++lLastSegId);
       GpiSetTag( hpsClient, lLastSegId);

       /**********************************************************************
       /* store the piece location
       /**********************************************************************
       sl.ptlLocation = ptl;

       /**********************************************************************
       /* compute the dimensions of the matching rects for BitBlt
       /**********************************************************************
       sl.rclBitBlt.xLeft   = ptl.x - 10;
       sl.rclBitBlt.yBottom = ptl.y - 10;
       sl.rclBitBlt.xRight  = ptl.x + 60;
       sl.rclBitBlt.yTop    = ptl.y + 60;
       if( ptl.x == ptlBotLeft.x)
         sl.rclBitBlt.xLeft += 10;
       if( ptl.y == ptlBotLeft.y)
         sl.rclBitBlt.yBottom += 10;
       if( (ptl.x + 50) == ptlTopRight.x)
         sl.rclBitBlt.xRight -= 10;
       if( (ptl.y + 50) == ptlTopRight.y)
         sl.rclBitBlt.yTop -= 10;

       /**********************************************************************
       /* draw one piece
       /**********************************************************************
       GpiBeginPath( hpsClient, 1L);
       GpiMove( hpsClient, &ptl);
       GpiPolyLine( hpsClient, 12L, aptlSides);
       GpiEndPath( hpsClient);
       GpiSetColor( hpsClient, CLR_BLACK);
       GpiLabel( hpsClient, FILLPATH);

       GpiSetClipPath( hpsClient, 0L, SCP_RESET);
       GpiBeginPath( hpsClient, 1L);
       GpiMove( hpsClient, &ptl);
       GpiPolyLine( hpsClient, 12L, aptlSides);
       GpiEndPath( hpsClient);
       GpiSetClipPath( hpsClient, 1L, SCP_AND);
       GpiLabel( hpsClient, BITBLT_TOP);
       GpiLabel( hpsClient, BITBLT_BOTTOM);

       GpiSetClipPath( hpsClient, 0L, SCP_RESET);
       GpiSetColor( hpsClient, CLR_RED);
       GpiMove( hpsClient, &ptl);
       GpiPolyLine( hpsClient, 12L, aptlSides);

       GpiCloseSegment( hpsClient);
       GpiSetSegmentAttrs( hpsClient, lLastSegId, ATTR_CHAINED, ATTR_ON);
       GpiSetSegmentAttrs( hpsClient, lLastSegId, ATTR_DETECTABLE, ATTR_ON);

       sl.lSegId = lLastSegId;
       sl.pslNext = NULL;
       sl.pslPrev = NULL;
       SetRect( &sl);
       SegListUpdate( ADD_TAIL_SEG, &sl);
     }
   }
   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Create the Temp and Drag bitmaps.
 /*
 /****************************************************************************
 BOOL
 PrepareBitmap()
 {
   bmpBitmapTemp    = bmpBitmapFile;
   bmpBitmapTemp.cx = LOUSHORT( (sizlMaxClient.cx * 6L) / 5L);
   bmpBitmapTemp.cy = LOUSHORT( (sizlMaxClient.cy * 6L) / 5L);
   hbmBitmapTemp    = GpiCreateBitmap( hpsBitmapTemp
                                     , &bmpBitmapTemp
                                     , 0L
                                     , NULL
                                     , NULL);
   if( !hbmBitmapTemp)
     return( FALSE);

   bmpBitmapDrag    = bmpBitmapFile;
   bmpBitmapDrag.cx = LOUSHORT( (sizlMaxClient.cx * 6L) / 5L);
   bmpBitmapDrag.cy = LOUSHORT( (sizlMaxClient.cy * 6L) / 5L);
   hbmBitmapDrag    = GpiCreateBitmap( hpsBitmapDrag
                                     , &bmpBitmapDrag
                                     , 0L
                                     , NULL
                                     , NULL);
   if( !hbmBitmapDrag)
     return( FALSE);
   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Create a memory DC and an associated PS.
 /*
 /****************************************************************************
 BOOL
 CreateBitmapHdcHps( phdc, phps)

 PHDC  phdc;
 PHPS  phps;
 {
   SIZEL    sizl;
   HDC           hdc;
   HPS           hps;

   hdc = DevOpenDC( habMain, OD_MEMORY, "*", 3L, (PDEVOPENDATA)&dop, NULL);
   if( !hdc)
     return( FALSE);

   sizl.cx = sizl.cy = 0L;
   hps = GpiCreatePS( habMain
                    , hdc
                    , &sizl
                    , PU_PELS | GPIA_ASSOC );
   if( !hps)
     return( FALSE);

   *phdc = hdc;
   *phps = hps;
   return( TRUE);
 }

 /****************************************************************************
 /*
 /* Get the bitmap from disk.
 /* Note that there are 2 formats for bitmap files, one of which is archaic.
 /* Both formats are supported here.  All new bitmaps should follow the format
 /* in BITMAPFILEHEADER.
 /*
 /****************************************************************************
 BOOL
 ReadBitmap( hfile)

 HFILE  hfile;
 {
     ULONG cScans;
     ULONG ulSize;         /* Number of bytes occupied by bitmap bits.
     USHORT cSegs;         /* Number of 64K segments in ulSize.
     USHORT cbExtra;         /* Bytes in last segment of ulSize.
     SEL sel;                 /* Base selector to file data.
     USHORT hugeshift;         /* Segment index shift value.
     USHORT cbRead1;         /* Number of bytes to read first call to DosRead
     USHORT cbRead2;         /* Number of bytes to read second call to DosRead
     USHORT cbRead;         /* Number of bytes read by DosRead.
     BOOL fRet = FALSE;         /* Function return code.
     USHORT  i;                 /* Generic loop index.
     FILESTATUS fsts;
     PBITMAPFILEHEADER pbfh;
     PRCBITMAP  rb;
     PBYTE pImage;


     /************************************************************************
     /* Find out how big the file is so we can read the whole thing in.
     /************************************************************************

     if( DosQFileInfo( hfile, 1, &fsts, sizeof(FILESTATUS)) != 0)
         goto ReadBitmap_close_file;

     ulSize  = fsts.cbFile;
     cSegs   = (USHORT)(ulSize/0x10000L);
     cbExtra = (USHORT)(ulSize%0x10000L);
     if (DosAllocHuge(cSegs, cbExtra, (PSEL)&sel, 0, 0))
         goto ReadBitmap_close_file;
     if (DosGetHugeShift( &hugeshift))
         goto ReadBitmap_free_bits;

     pImage = (PBYTE)MAKEP(sel, 0);
     rb           = (PRCBITMAP)pImage;
     pbfh   = (PBITMAPFILEHEADER)pImage;


     /************************************************************************
     /* Read the bits in from the file. The DosRead function allows a
     /* maximum of 64K-1 bytes read at a time.  We get around this
     /* by reading two 32K chunks for each 64K segment, and reading the
     /* last segment in one piece.
     /************************************************************************

     for (i = 0; i <= cSegs; ++i)
     {
         if (i < cSegs)
         {
             /* This segment is 64K bytes long, so split it up. */
             cbRead1 = 0x8000;
             cbRead2 = 0x8000;
         }
         else
         {
             /* This segment is less than 64K bytes long, so read it all. */
             cbRead1 = cbExtra;
             cbRead2 = 0;
         }

         /* There's a possibility that cbExtra will be 0, so check
          * to avoid an unnecessary system call.
          */
         if (cbRead1 > 0)
         {
             if (DosRead( hfile
                        , (PVOID)MAKEP(sel+(i<<hugeshift), 0)
                        , cbRead1
                        , &cbRead))
                 goto ReadBitmap_free_bits;
             if (cbRead1 != cbRead)
                 goto ReadBitmap_free_bits;
         }

         /* This will always be skipped on the last partial segment. */
         if (cbRead2 > 0)
         {
             if (DosRead( hfile
                        , (PVOID)MAKEP(sel+(i<<hugeshift), cbRead1)
                        , cbRead2
                        , &cbRead))
                 goto ReadBitmap_free_bits;
             if (cbRead2 != cbRead)
                 goto ReadBitmap_free_bits;
         }
     }


     /************************************************************************
     /* Tell GPI to put the bits into the thread's PS. The function returns
     /* the number of scan lines of the bitmap that were copied.  We want
     /* all of them at once.
     /************************************************************************

     if (pbfh->bmp.cbFix != sizeof(BITMAPINFOHEADER))
     {
         bmpBitmapFile.cx        = rb->bmWidth;
         bmpBitmapFile.cy        = rb->bmHeight;
         bmpBitmapFile.cPlanes        = rb->bmPlanes;
         bmpBitmapFile.cBitCount = rb->bmBitcount;
         hbmBitmapFile = GpiCreateBitmap( hpsBitmapFile
                                        , &bmpBitmapFile
                                        , 0L
                                        , NULL
                                        , NULL);
         if( !hbmBitmapFile)
             goto ReadBitmap_free_bits;
         GpiSetBitmap( hpsBitmapFile, hbmBitmapFile);

         pImage += rb->dwBitsOffset;
         rb->dwBitsOffset = sizeof(BITMAPINFOHEADER);
         cScans = GpiSetBitmapBits( hpsBitmapFile
                                  , 0L
                                  , (LONG)rb->bmHeight
                                  , pImage
                                  , (PBITMAPINFO)&(rb->dwBitsOffset));
         if (cScans != (ULONG)rb->bmHeight)  /* original number of scans ? */
             goto ReadBitmap_free_bits;
     }
     else
     {
         bmpBitmapFile.cx        = pbfh->bmp.cx;
         bmpBitmapFile.cy        = pbfh->bmp.cy;
         bmpBitmapFile.cPlanes        = pbfh->bmp.cPlanes;
         bmpBitmapFile.cBitCount = pbfh->bmp.cBitCount;
         hbmBitmapFile = GpiCreateBitmap( hpsBitmapFile
                                        , &bmpBitmapFile
                                        , 0L
                                        , NULL
                                        , NULL);
         if( !hbmBitmapFile)
             goto ReadBitmap_free_bits;
         GpiSetBitmap( hpsBitmapFile, hbmBitmapFile);

         cScans = GpiSetBitmapBits( hpsBitmapFile
                                  , 0L
                                  , (LONG)pbfh->bmp.cy
                                  , pImage + pbfh->offBits
                                  , (PBITMAPINFO)&(pbfh->bmp));
         if (cScans != (ULONG)pbfh->bmp.cy)  /* original number of scans ? */
             goto ReadBitmap_free_bits;
     }

     fRet = TRUE;     /* okey-dokey */


     /************************************************************************
     /* Close the file, free the buffer space and leave.  This is a
     /* common exit point from the function.  Since the same cleanup
     /* operations need to be performed for such a large number of
     /* possible error conditions, this is concise way to do the right
     /* thing.
     /************************************************************************

 ReadBitmap_free_bits:
     DosFreeSeg( sel);
 ReadBitmap_close_file:
     DosClose( hfile);
     return fRet;
 }


 LFCMD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFCMD.C

 /************************************************************************
 *
 *   lfcmd.c -- Subroutines for menu command processing for LineFractal.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_BITMAPFILEFORMAT
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSMEMMGR
 #define INCL_DOSPROCESS
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>

 #include "opendlg.h"

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFCMD
 #define INCL_LFDLG
 #define INCL_LFMAIN
 #define INCL_LFTHREAD
 #define INCL_LFPS
 #define INCL_LFDRAW
 #define INCL_LFUTIL
 #define INCL_LFFILE
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Macros
 *
 *   Helper macros for repetitious code.
 *
 ************************************************************************/

 #define UPDATE_MENU_BOOL(var, val)                                \
         {                                                        \
             TOGGLE_BOOL((var));                                 \
             TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var));        \
         }

 #define UPDATE_MENU_LIST(var, val)                                \
         {                                                        \
             UNCHECK_MENU_ITEM(global.hwndFrame, (var));         \
             (var) = (val);                                        \
             CHECK_MENU_ITEM(global.hwndFrame, (var));                \
         }




 /************************************************************************
 *
 *   Global Variables
 *
 *   global        Global data block.
 *   aulRops        Array of BitBlt rops available for pasting.
 *   aXform        Array of built-in transforms and default positioning
 *                  and sizing information.
 *
 ************************************************************************/

 extern GLOBALDATA global;
 extern XFORMDATA  aXform[];

 ULONG aulRops[] =
 {
       ROP_SRCCOPY,
       ROP_SRCAND,
       ROP_SRCPAINT,
       ROP_SRCINVERT,
     (~ROP_SRCCOPY)   & 0xFF,
     (~ROP_SRCAND)    & 0xFF,
     (~ROP_SRCPAINT)  & 0xFF,
     (~ROP_SRCINVERT) & 0xFF
 };

 char *pszAttrs = "Previous attributes not read yet.";




 /************************************************************************
 *
 *   LfCommand
 *
 *   Dispatches menu commands to the proper handlers.
 *
 ************************************************************************/

 VOID
 LfCommand(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     switch (id)
     {

     /********************************************************************
     *
     *        File menu
     *
     ********************************************************************/

     case IDM_LOAD:
     case IDM_SAVE:
     case IDM_ABOUT:
         LfFileMenu(hwnd, id);
         break;

     /********************************************************************
     *
     *        Edit menu
     *
     ********************************************************************/

     case IDM_SELECT:
     case IDM_SELECTALL:
     case IDM_CUT:
     case IDM_COPY:
     case IDM_PASTE:
     case IDM_USECLIPBRD:
     case IDM_PASTES:
     case IDM_PASTEDSA:
     case IDM_PASTEDSO:
     case IDM_PASTEDSX:
     case IDM_PASTESN:
     case IDM_PASTEDSAN:
     case IDM_PASTEDSON:
     case IDM_PASTEDSXN:
         LfEditMenu(hwnd, id);
         break;

     /********************************************************************
     *
     *        Thread menu
     *
     ********************************************************************/

     case IDM_DCPOSTSCRIPT:
     case IDM_DCPROPRINTER:
     case IDM_DCDIRECT:
     case IDM_DCMEMORY:
     case IDM_EDITTHREADPARAMS:
     case IDM_THR0TOTOP:
     case IDM_THR1TOTOP:
     case IDM_THR2TOTOP:
     case IDM_THR3TOTOP:
     case IDM_THR4TOTOP:
     case IDM_THR5TOTOP:
     case IDM_THR6TOTOP:
     case IDM_THR7TOTOP:
     case IDM_THR8TOTOP:
     case IDM_THR9TOTOP:
     case IDM_KILLTOPTHREAD:
     case IDM_KILLALLTHREADS:
     case IDM_AUTOSWITCH:
     case IDM_SWITCHDELAY:
     case IDM_UPDATEMENUS:
     case IDM_THREADINHERITANCE:
         LfThreadMenu(hwnd, id);
         break;

     /********************************************************************
     *
     *        Control menu
     *
     ********************************************************************/

     case IDM_CLEARBITMAP:
     case IDM_COPYBITMAPTOSCREEN:
     case IDM_SQUAREBITMAP:
     case IDM_RESIZEBITMAPTOWINDOW:
     case IDM_MOUSECHANGESRECURSION:
     case IDM_ANIMATEPTR:
     case IDM_PTRPREFERENCES:
     case IDM_TIMERDELAY:
         if (LfControlMenu(hwnd, id))
             goto lfcmd_redraw;
         break;

     /********************************************************************
     *
     *        Fractal menu
     *
     ********************************************************************/

     case IDM_SHARKTOOTH:
     case IDM_SAWTOOTH:
     case IDM_KOCH:
     case IDM_SPACEFILLER:
     case IDM_STOVE:
     case IDM_SQUAREWAVE:
     case IDM_HOURGLASS:
     case IDM_FRACREDRAW:
         if (LfFractalMenu(hwnd, id))
             goto lfcmd_redraw;
         break;

     /********************************************************************
     *
     *        Primitive menu
     *
     ********************************************************************/

     case IDM_POLYLINE:
     case IDM_POLYFILLET:
     case IDM_POLYSPLINE:
     case IDM_POLYMARKER:
     case IDM_PEANO:
     case IDM_PRIMREDRAW:
         if (LfPrimitiveMenu(hwnd, id))
             goto lfcmd_redraw;
         break;

     /********************************************************************
     *
     *        Attributes menu
     *
     ********************************************************************/

     case IDM_LINEATTRS:
     case IDM_MARKERATTRS:
     case IDM_AREAATTRS:
     case IDM_IMAGEATTRS:
     case IDM_MISCATTRS:
     case IDM_DIMENSIONS:
     case IDM_DRAGDIMS:
     case IDM_ATTRREDRAW:
         if (LfAttributesMenu(hwnd, id))
             goto lfcmd_redraw;
         break;

     /********************************************************************
     *
     *        Environment menu
     *
     ********************************************************************/

     case IDM_AUTORESIZE:
     case IDM_AUTOSELECTDIMS:
     case IDM_CLEARONREDRAW:
     case IDM_AUTOSTARTREDRAW:
     case IDM_COLLECTBOUNDS:
         LfEnvironmentMenu(hwnd, id);
         break;

     /********************************************************************
     *
     *        Control menu, continued.  These two commands dovetail
     *        conveniently,  so they are placed out of sequence here.
     *
     ********************************************************************/

 lfcmd_redraw:
     case IDM_REDRAW:
         DosSemClear(&(global.pThrTop)->lSemRedraw);

         /* Fall through to restart the drawing. */

     case IDM_ABORT:
         global.pThrTop->fInterrupted = TRUE;
         break;
     }
 }




 /************************************************************************
 *
 *   LfFileMenu
 *
 *   These commands allow the loading and saving of bitmaps from threads
 *   with memory DC's.  The about box shows the version number, title,
 *   and author.
 *
 ************************************************************************/

 VOID
 LfFileMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     switch (id)
     {
     case IDM_LOAD:
         if (global.pThrTop->dcType == IDM_DCMEMORY)
             LfReadFile(hwnd, global.pThrTop);
         else
             MyMessageBox(hwnd, "Cannot load bitmap into a\nnon-memory DC.");
         break;

     case IDM_SAVE:
         if (global.pThrTop->dcType == IDM_DCMEMORY)
             LfWriteFile(hwnd, global.pThrTop);
         else
             MyMessageBox(hwnd, "Cannot save bitmap from a\nnon-memory DC.");
         break;

     case IDM_ABOUT:
         WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)AboutDlg, (HMODULE) NULL,
                        (USHORT) IDR_ABOUTDLG, NULL );
         break;
     }
 }




 /************************************************************************
 *
 *   LfEditMenu
 *
 *   These commands allow for cutting, copying, and pasting of rectangles
 *   of bits.  The user can drag a rectangle or select the entire surface.
 *   The GpiBitBlt rop to use when pasting can be any of those listed.
 *   The user can specify that the system clipboard be used for storing
 *   the cut or copied bits, or that a private bitmap be used.
 *
 ************************************************************************/

 VOID
 LfEditMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     switch (id)
     {
     case IDM_SELECT:
         global.fSelecting = TRUE;
         break;

     case IDM_SELECTALL:
         LfSelectAll(hwnd);
         break;

     case IDM_CUT:
         if (global.fShowSelectRc)
             LfCut(hwnd);
         break;

     case IDM_COPY:
         if (global.fShowSelectRc)
             LfCopy(hwnd);
         break;

     case IDM_PASTE:
         if (global.fHaveSelectRc)
         {
             HPS hps;

             global.fPasting = TRUE;
             if (global.fShowSelectRc && global.fTempSelect)
             {
                 hps = WinGetPS(hwnd);
                 LfShowSelectRc(hps, &global.rclSelect);
                 WinReleasePS(hps);
                 global.fShowSelectRc = FALSE;
                 global.fTempSelect = FALSE;
             }
             if (!global.fShowSelectRc)
             {
                 global.fShowSelectRc = TRUE;
                 hps = WinGetPS(hwnd);
                 LfShowSelectRc(hps, &global.rclCutCopy);
                 WinReleasePS(hps);
             }
         }
         break;

     case IDM_USECLIPBRD:
         UPDATE_MENU_BOOL(global.fUseClipbrd, IDM_USECLIPBRD);
         break;

     /********************************************************************/

     case IDM_PASTES:
     case IDM_PASTEDSA:
     case IDM_PASTEDSO:
     case IDM_PASTEDSX:
     case IDM_PASTESN:
     case IDM_PASTEDSAN:
     case IDM_PASTEDSON:
     case IDM_PASTEDSXN:
         UNCHECK_MENU_ITEM(global.hwndFrame, global.usRopIndex+IDM_PASTES);
         global.usRopIndex = id - IDM_PASTES;
         CHECK_MENU_ITEM(global.hwndFrame, global.usRopIndex+IDM_PASTES);
         global.ulPasteROP = aulRops[global.usRopIndex];
         break;
     }
 }




 /************************************************************************
 *
 *   LfThreadMenu
 *
 *   These commands allow the creation, viewing, editing, and deletion
 *   of up to 10 independently drawing threads.        The middle group
 *   allow for simple animation by cycling through the threads at
 *   a user-selectable rate.  The updating of menus can be disabled to
 *   allow a faster thread-switch rate.        The last group determine how
 *   new threads will look and act.  If inheritance is enabled, a new
 *   thread will be drawn with the attributes of the top thread, otherwise
 *   it will use the program's default values.
 *
 ************************************************************************/

 VOID
 LfThreadMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     static char *szError1 = "Could not start thread.";

     switch (id)
     {
     case IDM_DCPOSTSCRIPT:
     case IDM_DCPROPRINTER:
         break;        /* not implemented yet */

     case IDM_DCDIRECT:
         {
             PTHR pthr;

             if (global.cThr < MAX_THREADS)
                 if (pthr = LfStartThread(IDM_DCDIRECT))
                 {
                     (global.aThr)[global.cThr] = pthr;
                     ++global.cThr;
                     pthr->dcType = id;

                     LfBringThreadToTop(pthr);
                     break;
                 }
             MyMessageBox(hwnd, szError1);
         }
         break;

     case IDM_DCMEMORY:

         if (global.cThr < MAX_THREADS)
         {
             if (global.fFirstThread)
             {
                 global.bm.cx            = (USHORT) (global.rcl.xRight - globa
                 global.bm.cy            = (USHORT) (global.rcl.yTop   - globa
                 global.bm.cPlanes   = 0;
                 global.bm.cBitCount = 0;
                 global.fFirstThread = FALSE;
             }
             else if (global.fThreadInheritance)
             {
                 global.bm.cx            = (USHORT) global.pThrTop->rcl.xRight
                 global.bm.cy            = (USHORT) global.pThrTop->rcl.yTop;
                 global.bm.cPlanes   = global.pThrTop->cPlanes;
                 global.bm.cBitCount = global.pThrTop->cBitCount;
             }
             else
             {
                 global.bm.cx            = (USHORT) (global.rcl.xRight - globa
                 global.bm.cy            = (USHORT) (global.rcl.yTop   - globa
                 global.bm.cPlanes   = 0;
                 global.bm.cBitCount = 0;
                 if (!WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)BitmapParamsDlg, (
                               (USHORT) IDR_BITMAPPARAMSDLG, NULL ))
                     break;        /* if user hit "cancel", don't start thread
             }

             {
                 PTHR pthr;

                 if (pthr = LfStartThread(IDM_DCMEMORY))
                 {
                     (global.aThr)[global.cThr] = pthr;
                     ++global.cThr;
                     pthr->dcType = id;

                     LfBringThreadToTop(pthr);
                     break;
                 }
             }
         }
         MyMessageBox(hwnd, szError1);
         break;

     case IDM_EDITTHREADPARAMS:
         if (global.pThrTop->dcType == IDM_DCMEMORY)
         {
             USHORT cxT, cyT, cpT, cbT;

             cxT = global.bm.cx              = (USHORT) global.pThrTop->rcl.xR
             cyT = global.bm.cy              = (USHORT) global.pThrTop->rcl.yT
             cpT = global.bm.cPlanes   = global.pThrTop->cPlanes;
             cbT = global.bm.cBitCount = global.pThrTop->cBitCount;

             if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)BitmapParamsDlg, (HMODU
                           (USHORT) IDR_BITMAPPARAMSDLG, NULL ))
                 if ((cxT != global.bm.cx)      ||
                     (cyT != global.bm.cy)      ||
                     (cpT != global.bm.cPlanes) ||
                     (cbT != global.bm.cBitCount))
                     LfResizePS(global.pThrTop);
         }
         else
             MyMessageBox(hwnd, "No editable DC parameters.");
         break;

     case IDM_THR0TOTOP:
     case IDM_THR1TOTOP:
     case IDM_THR2TOTOP:
     case IDM_THR3TOTOP:
     case IDM_THR4TOTOP:
     case IDM_THR5TOTOP:
     case IDM_THR6TOTOP:
     case IDM_THR7TOTOP:
     case IDM_THR8TOTOP:
     case IDM_THR9TOTOP:
         {
             USHORT i;

             if ((i = id-IDM_THR0TOTOP) < global.cThr)
                 LfBringThreadToTop(global.aThr[i]);
         }
         break;

     case IDM_KILLTOPTHREAD:
         {
             PTHR pthr;

             pthr = global.pThrTop;
             if (global.cThr > 1)
             {
                 USHORT i;

                 if ((i = global.iThrTop+1) == global.cThr)
                     i -= 2;
                 LfBringThreadToTop(global.aThr[i]);
             }
             else
                 LfBringThreadToTop(NULL);

             LfKillThread(pthr);
             LfBringThreadToTop(global.pThrTop);     /* update global.iThrTop

             /* If no threads left, then start over. */
             if (global.cThr == 0)
             {
                 global.fFirstThread = TRUE;
                 WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_DCMEMORY, 0);
             }
         }
         break;

     case IDM_KILLALLTHREADS:
         {
             SHORT i;

             /* Delete them all and start over. */
             LfBringThreadToTop(NULL);
             for (i = global.cThr-1; i >= 0; --i)
                 LfKillThread(global.aThr[i]);
             global.fFirstThread = TRUE;
             WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_DCMEMORY, 0);
         }
         break;

     /********************************************************************/

     case IDM_AUTOSWITCH:
         UPDATE_MENU_BOOL(global.fAutoSwitch, IDM_AUTOSWITCH);
         if (global.fAutoSwitch)
             WinStartTimer(global.hab, global.hwnd, IDT_AUTOSWITCH, global.usS
         else
             WinStopTimer(global.hab, global.hwnd, IDT_AUTOSWITCH);
         break;

     case IDM_SWITCHDELAY:
         WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)SwitchDelayDlg, (HMODULE) NULL,
                       (USHORT) IDR_SWITCHDELAYDLG, NULL );
         break;

     case IDM_UPDATEMENUS:
         UPDATE_MENU_BOOL(global.fUpdateMenusOnThreadSwitch, IDM_UPDATEMENUS);
         break;

     /********************************************************************/

     case IDM_THREADINHERITANCE:
         UPDATE_MENU_BOOL(global.fThreadInheritance, IDM_THREADINHERITANCE);
         break;

     }
 }




 /************************************************************************
 *
 *   LfControlMenu
 *
 *   These commands cause global effects.  The top set cause immediate
 *   results.  The bottom set determine how the program reacts to
 *   certain events (mouse movement and button messages, timer messages).
 *
 ************************************************************************/

 BOOL
 LfControlMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     BOOL fRedraw = FALSE;

     switch (id)
     {
     case IDM_CLEARBITMAP:
         LfClearRect(global.pThrTop, NULL);
         break;

     case IDM_COPYBITMAPTOSCREEN:
         if (global.pThrTop->hps)
             WinInvalidateRect(global.hwnd, &(global.pThrTop)->rcl, FALSE);
         break;

     case IDM_SQUAREBITMAP:
         LfSquareBitmap(hwnd);
         break;

     case IDM_RESIZEBITMAPTOWINDOW:
         LfResizeBitmapToWindow();
         fRedraw = TRUE;
         break;

     /********************************************************************/

     case IDM_MOUSECHANGESRECURSION:
         UPDATE_MENU_BOOL(global.fMouseChangesRecursion, IDM_MOUSECHANGESRECUR
         break;

     case IDM_ANIMATEPTR:
         UPDATE_MENU_BOOL(global.fAnimatePtr, IDM_ANIMATEPTR);
         if (global.fAnimatePtr)
             global.usPtrIncr = 1;
         else
         {
             global.usPtrIncr = 0;
             global.usCurPtr  = global.usUserPtr;
         }
         break;

     case IDM_PTRPREFERENCES:
         WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)PtrPreferencesDlg, (HMODULE) NU
                       (USHORT) IDR_PTRPREFERENCESDLG, NULL );
         break;

     case IDM_TIMERDELAY:
         WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)TimerDelayDlg, (HMODULE) NULL,
                       (USHORT) IDR_TIMERDELAYDLG, NULL );
         break;
     }

     return fRedraw;
 }




 /************************************************************************
 *
 *   LfFractalMenu
 *
 *   These commands select the fractal similarity transform, i.e. the
 *   basic fractal shape, for a thread.
 *
 ************************************************************************/

 BOOL
 LfFractalMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     BOOL fRedraw = FALSE;

     switch (id)
     {
     case IDM_SHARKTOOTH:
     case IDM_SAWTOOTH:
     case IDM_KOCH:
     case IDM_SPACEFILLER:
     case IDM_STOVE:
     case IDM_SQUAREWAVE:
     case IDM_HOURGLASS:
         LfSelectXform(hwnd, global.pThrTop, id);
         if (global.pThrTop->fFracRedraw)
             fRedraw = TRUE;
         break;

     /********************************************************************/

     case IDM_FRACREDRAW:
         UPDATE_MENU_BOOL(global.pThrTop->fFracRedraw, IDM_FRACREDRAW);
         break;
     }

     return fRedraw;
 }




 /************************************************************************
 *
 *   LfPrimitiveMenu
 *
 *   These commands select the drawing primitive used to output the
 *   fractal for a thread.
 *
 ************************************************************************/

 BOOL
 LfPrimitiveMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     BOOL fRedraw = FALSE;

     switch (id)
     {
     case IDM_POLYLINE:
     case IDM_POLYFILLET:
     case IDM_POLYSPLINE:
     case IDM_POLYMARKER:
     case IDM_PEANO:
         LfSelectPrim(hwnd, global.pThrTop, id);
         if (global.pThrTop->fPrimRedraw)
             fRedraw = TRUE;
         break;

     /********************************************************************/

     case IDM_PRIMREDRAW:
         UPDATE_MENU_BOOL(global.pThrTop->fPrimRedraw, IDM_PRIMREDRAW);
         break;
     }

     return fRedraw;
 }




 /************************************************************************
 *
 *   LfAttributesMenu
 *
 *   These commands allow the user to alter all the drawing attributes
 *   of a fractal (color, mix mode, marker symbol, etc.), as well as
 *   some fractal-related attributes such as width, height, offset,
 *   number of sides to polygonal frame, and rotation.  Each thread
 *   has an independent set of all these attributes.
 *
 ************************************************************************/

 BOOL
 LfAttributesMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     BOOL fRedraw = FALSE;

     switch (id)
     {
     case IDM_LINEATTRS:
         if (global.fUpdateAttrs && (global.flLineAttrs & LFA_LINEALL))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)LineAttrsDlg, (HMODULE) NUL
                       (USHORT) IDR_LINEATTRSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_MARKERATTRS:
         if (global.fUpdateAttrs && (global.flMarkerAttrs & LFA_MARKALL))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)MarkerAttrsDlg, (HMODULE) N
                       (USHORT) IDR_MARKERATTRSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_AREAATTRS:
         if (global.fUpdateAttrs && (global.flAreaAttrs & LFA_AREAALL))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)AreaAttrsDlg, (HMODULE) NUL
                       (USHORT) IDR_AREAATTRSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_IMAGEATTRS:
         if (global.fUpdateAttrs && (global.flImageAttrs & LFA_IMAGEALL))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)ImageAttrsDlg, (HMODULE) NU
                       (USHORT) IDR_IMAGEATTRSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_MISCATTRS:
         if (global.fUpdateAttrs && (global.flMiscAttrs & (LFA_RECURSION |
                 LFA_CPTMAX | LFA_POLYGONSIDES | LFA_ROTATION)))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)MiscAttrsDlg, (HMODULE) NUL
                       (USHORT) IDR_MISCATTRSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_DIMENSIONS:
         if (global.fUpdateAttrs && (global.flMiscAttrs & (LFA_XOFF |
                 LFA_YOFF | LFA_XSCALE | LFA_YSCALE)))
             MyMessageBox(hwnd, pszAttrs);
         if (WinDlgBox( HWND_DESKTOP, hwnd, (PFNWP)DimensionsDlg, (HMODULE) NU
                       (USHORT) IDR_DIMENSIONSDLG, NULL ))
             fRedraw = TRUE;
         break;

     case IDM_DRAGDIMS:
         global.fTracking = TRUE;
         break;

     /********************************************************************/

     case IDM_ATTRREDRAW:
         UPDATE_MENU_BOOL(global.pThrTop->fAttrRedraw, IDM_ATTRREDRAW);
         break;
     }

     if (fRedraw)
         if (global.pThrTop->fAttrRedraw)
             return TRUE;
     return FALSE;
 }




 /************************************************************************
 *
 *   LfEnvironmentMenu
 *
 *   These commands alter the way a thread reacts to system and program
 *   events by toggling certain flags.  Each thread has its own
 *   environment flags.
 *
 ************************************************************************/

 VOID
 LfEnvironmentMenu(hwnd, id)
 HWND hwnd;
 USHORT id;
 {
     switch (id)
     {
     case IDM_AUTORESIZE:
         UPDATE_MENU_BOOL(global.pThrTop->fAutoSizePS, IDM_AUTORESIZE);
         break;

     case IDM_AUTOSELECTDIMS:
         UPDATE_MENU_BOOL(global.pThrTop->fAutoSelectDims, IDM_AUTOSELECTDIMS)
         break;

     case IDM_CLEARONREDRAW:
         UPDATE_MENU_BOOL(global.pThrTop->fClearOnRedraw, IDM_CLEARONREDRAW);
         break;

     case IDM_AUTOSTARTREDRAW:
         UPDATE_MENU_BOOL(global.pThrTop->fAutoStartRedraw, IDM_AUTOSTARTREDRA
         if (global.pThrTop->fAutoStartRedraw)
             LfStartRedrawTimer();
         break;

     case IDM_COLLECTBOUNDS:
         UPDATE_MENU_BOOL(global.pThrTop->fCollectBounds, IDM_COLLECTBOUNDS);
         break;

     }

     hwnd;        /* this prevents an "unused variable" warning message */
 }




 /************************************************************************
 *
 *   LfSelectDimension
 *
 *   Set the dimensions of the next fractal by dragging a rectangle
 *   on the screen.
 *
 ************************************************************************/

 VOID
 LfSelectDimension(hwnd, pt)
 HWND hwnd;
 POINTS pt;
 {
     TRACKINFO ti;
     LONG lXExt, lYExt;


     WinSendMsg(global.hwndFrame, WM_QUERYTRACKINFO, (MPARAM)TF_MOVE, (MPARAM)

     ti.cxBorder   = 1;
     ti.cyBorder   = 1;
     ti.rclTrack.xLeft        = (LONG)pt.x;
     ti.rclTrack.yBottom = (LONG)pt.y;
     ti.rclTrack.xRight        = (LONG)pt.x;
     ti.rclTrack.yTop        = (LONG)pt.y;
     ti.fs = TF_RIGHT | TF_TOP;
     ti.ptlMinTrackSize.x = 1L;
     ti.ptlMinTrackSize.y = 1L;

     if (WinTrackRect(hwnd, NULL, &ti))
     {
         lXExt = (global.pThrTop->rcl).xRight - (global.pThrTop->rcl).xLeft;
         lYExt = (global.pThrTop->rcl).yTop   - (global.pThrTop->rcl).yBottom;

         global.dblXScale = (double)(ti.rclTrack.xRight - ti.rclTrack.xLeft)
         global.dblYScale = (double)(ti.rclTrack.yTop   - ti.rclTrack.yBottom)
         global.dblXOff         = (double) ti.rclTrack.xLeft        / (double)
         global.dblYOff         = (double) ti.rclTrack.yBottom / (double)lYExt

         global.flMiscAttrs |= LFA_XSCALE | LFA_YSCALE | LFA_XOFF | LFA_YOFF;

         global.pThrTop->fUpdateAttrs = TRUE;
         global.fUpdateAttrs             = TRUE;
     }
 }




 /************************************************************************
 *
 *   LfSquareBitmap
 *
 *   Resize the window horizontally to so that the client area is a
 *   square.
 *
 ************************************************************************/

 VOID
 LfSquareBitmap(hwnd)
 HWND hwnd;
 {
     SWP swp;
     HWND hwndDesk;
     RECTL rcl;

     if (hwnd == global.hwnd)
         if (hwndDesk = WinQueryDesktopWindow(global.hab, NULL))
             if (WinQueryWindowPos(global.hwndFrame, (PSWP)&swp))
             {
                 rcl.xLeft   = 0L;
                 rcl.yBottom = 0L;
                 rcl.xRight  = swp.cx;
                 rcl.yTop    = swp.cy;
                 if (WinCalcFrameRect(global.hwndFrame, &rcl, TRUE))
                 {
                     rcl.xRight        = (SHORT)((LONG)rcl.yTop   *
                                   global.pThrTop->AspectRatio.lHorz /
                                   global.pThrTop->AspectRatio.lVert);

                     if (WinCalcFrameRect(global.hwndFrame, &rcl, FALSE))
                         WinSetWindowPos(global.hwndFrame, 0, swp.x, swp.y,
                             (SHORT)rcl.xRight, (SHORT)rcl.yTop,
                             SWP_SIZE);
                 }
             }
 }




 /************************************************************************
 *
 *   LfResizeBitmapToWindow
 *
 *   Resize the bitmap to match the windows dimensions exactly.
 *
 ************************************************************************/

 VOID
 LfResizeBitmapToWindow()
 {
     global.bm.cx = (USHORT) (global.rcl.xRight - global.rcl.xLeft);
     global.bm.cy = (USHORT) (global.rcl.yTop   - global.rcl.yBottom);
     global.bm.cPlanes        = global.pThrTop->cPlanes;
     global.bm.cBitCount = global.pThrTop->cBitCount;

     LfResizePS(global.pThrTop);
 }




 /************************************************************************
 *
 *   LfSelect
 *
 *   Select a rectangle to cut or copy to the clipboard.
 *
 ************************************************************************/

 VOID
 LfSelect(hwnd, pt)
 HWND hwnd;
 POINTS pt;
 {
     HPS hps;
     TRACKINFO ti;
     PRECTL prcl;


     if (global.fShowSelectRc)
     {
         global.fShowSelectRc = FALSE;
         hps = WinGetPS(hwnd);
         if (global.fTempSelect)
         {
             prcl = &global.rclSelect;
             global.fTempSelect = FALSE;
         }
         else
             prcl = &global.rclCutCopy;
         LfShowSelectRc(hps, prcl);
         WinReleasePS(hps);
     }

     WinSendMsg(global.hwndFrame, WM_QUERYTRACKINFO, (MPARAM)TF_MOVE, (MPARAM)

     ti.cxBorder = 1;
     ti.cyBorder = 1;
     ti.rclTrack.xLeft        = (LONG)pt.x;
     ti.rclTrack.yBottom = (LONG)pt.y;
     ti.rclTrack.xRight        = (LONG)pt.x;
     ti.rclTrack.yTop        = (LONG)pt.y;
     ti.fs = TF_RIGHT | TF_TOP;
     ti.ptlMinTrackSize.x = 1L;
     ti.ptlMinTrackSize.y = 1L;

     if (WinTrackRect(hwnd, NULL, &ti))
     {
         global.rclSelect.xLeft         = ti.rclTrack.xLeft;
         global.rclSelect.yBottom = ti.rclTrack.yBottom;
         global.rclSelect.xRight  = ti.rclTrack.xRight;
         global.rclSelect.yTop         = ti.rclTrack.yTop;

         global.fShowSelectRc = TRUE;
         global.fTempSelect   = TRUE;
         hps = WinGetPS(hwnd);
         LfShowSelectRc(hps, &global.rclSelect);
         WinReleasePS(hps);
     }
 }




 /************************************************************************
 *
 *   LfSelectAll
 *
 *   Select the entire surface to cut or copy to the clipboard.
 *
 ************************************************************************/

 VOID
 LfSelectAll(hwnd)
 HWND hwnd;
 {
     HPS hps;

     global.rclSelect = global.pThrTop->rcl;

     global.fShowSelectRc = TRUE;
     global.fTempSelect         = TRUE;
     hps = WinGetPS(hwnd);
     LfShowSelectRc(hps, &global.rclSelect);
     WinReleasePS(hps);
 }




 /************************************************************************
 *
 *   LfCut
 *
 *   Cuts the selected rectangle from the bitmap and puts it in the
 *   clipboard.
 *
 ************************************************************************/

 VOID
 LfCut(hwnd)
 HWND hwnd;
 {
     if (LfCopy(hwnd))
         LfClearRect(global.pThrTop, &global.rclCutCopy);
 }




 /************************************************************************
 *
 *   LfCopy
 *
 *   Copies the selected rectangle from the bitmap the clipboard.
 *
 ************************************************************************/

 BOOL
 LfCopy(hwnd)
 HWND hwnd;
 {
     HPS hps;
     PRECTL prcl;
     SIZEL size;
     BITMAPINFOHEADER bminfo;
     POINTL  aptl[4];



     global.fShowSelectRc = FALSE;
     hps = WinGetPS(hwnd);
     if (global.fTempSelect)
     {
         prcl = &global.rclSelect;
         global.fTempSelect = FALSE;
     }
     else
         prcl = &global.rclCutCopy;
     LfShowSelectRc(hps, prcl);
     WinReleasePS(hps);

     if (global.fUseClipbrd)
     {
         if (WinOpenClipbrd(global.hab))
         {
             global.fHaveSelectRc = TRUE;
             global.rclCutCopy.xLeft   = global.rclSelect.xLeft;
             global.rclCutCopy.yBottom = global.rclSelect.yBottom;
             global.rclCutCopy.xRight  = global.rclSelect.xRight;
             global.rclCutCopy.yTop    = global.rclSelect.yTop;

             /* copy bits here */

             WinCloseClipbrd(global.hab);

             return TRUE;
         }
     }
     else
     {
         global.fHaveSelectRc = TRUE;
         global.rclCutCopy.xLeft   = global.rclSelect.xLeft;
         global.rclCutCopy.yBottom = global.rclSelect.yBottom;
         global.rclCutCopy.xRight  = global.rclSelect.xRight;
         global.rclCutCopy.yTop          = global.rclSelect.yTop;

         if (global.fHaveCutCopyDC)
         {
             GpiSetBitmap(global.CutCopyPS, NULL);
             GpiDeleteBitmap(global.CutCopyBM);
             GpiDestroyPS(global.CutCopyPS);
             DevCloseDC(global.CutCopyDC);
             global.fHaveCutCopyDC = FALSE;
         }

         global.CutCopyDC = DevOpenDC(global.hab, OD_MEMORY, "*", 0L, NULL, NU
         if (!global.CutCopyDC)
             return FALSE;

         size.cx = 0L;
         size.cy = 0L;
         global.CutCopyPS = GpiCreatePS(global.hab, global.CutCopyDC, &size,
                                  PU_PELS|GPIT_MICRO|GPIA_ASSOC);
         if (!global.CutCopyPS)
             goto lfc_error_close_dc;


         bminfo.cbFix = sizeof(BITMAPINFOHEADER);
         bminfo.cx = (USHORT) (global.rclCutCopy.xRight - global.rclCutCopy.xL
         bminfo.cy = (USHORT) (global.rclCutCopy.yTop   - global.rclCutCopy.yB
         bminfo.cPlanes         = global.pThrTop->cPlanes;
         bminfo.cBitCount = global.pThrTop->cBitCount;
         global.CutCopyBM = GpiCreateBitmap(global.CutCopyPS, &bminfo, 0L, 0L,
         if (!global.CutCopyBM)
             goto lfc_error_destroy_ps;
         GpiSetBitmap(global.CutCopyPS, global.CutCopyBM);

         aptl[0].x = 0L;
         aptl[0].y = 0L;
         aptl[1].x = global.rclCutCopy.xRight - global.rclCutCopy.xLeft;
         aptl[1].y = global.rclCutCopy.yTop   - global.rclCutCopy.yBottom;
         aptl[2].x = global.rclCutCopy.xLeft;
         aptl[2].y = global.rclCutCopy.yBottom;

         GpiBitBlt(global.CutCopyPS, global.pThrTop->hps, 3L, aptl, ROP_SRCCOP

         global.fHaveCutCopyDC = TRUE;
         return TRUE;


 lfc_error_destroy_ps:
         GpiDestroyPS(global.CutCopyPS);
 lfc_error_close_dc:
         DevCloseDC(global.CutCopyDC);
         return FALSE;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   LfPaste
 *
 *   Pastes the selected rectangle from the clipboard to the bitmap.
 *   Hides the selection rectangle before calling WinTrackRect.
 *
 ************************************************************************/

 VOID
 LfPaste(hwnd)
 HWND hwnd;
 {
     HPS hps;
     PRECTL prcl;
     TRACKINFO ti;
     POINTL    aptl[4];


     if (global.fShowSelectRc)
     {
         global.fShowSelectRc = FALSE;
         hps = WinGetPS(hwnd);
         if (global.fTempSelect)
         {
             prcl = &global.rclSelect;
             global.fTempSelect = FALSE;
         }
         else
             prcl = &global.rclCutCopy;
         LfShowSelectRc(hps, prcl);
         WinReleasePS(hps);
     }

     WinSendMsg(global.hwndFrame, WM_QUERYTRACKINFO, (MPARAM)TF_MOVE, (MPARAM)

     ti.cxBorder = 1;
     ti.cyBorder = 1;
     ti.rclTrack.xLeft        = global.rclCutCopy.xLeft;
     ti.rclTrack.yBottom = global.rclCutCopy.yBottom;
     ti.rclTrack.xRight        = global.rclCutCopy.xRight;
     ti.rclTrack.yTop        = global.rclCutCopy.yTop;
     ti.fs = TF_MOVE;
     ti.ptlMinTrackSize.x = 1L;
     ti.ptlMinTrackSize.y = 1L;

     if (WinTrackRect(hwnd, NULL, &ti))
     {
         if (global.fUseClipbrd)
         {
             if (WinOpenClipbrd(global.hab))
             {
                 /* copy bits here */
                 WinCloseClipbrd(global.hab);
             }
         }
         else
         {
             aptl[0].x = ti.rclTrack.xLeft;
             aptl[0].y = ti.rclTrack.yBottom;
             aptl[1].x = ti.rclTrack.xLeft   + (global.rclCutCopy.xRight - glo
             aptl[1].y = ti.rclTrack.yBottom + (global.rclCutCopy.yTop
             aptl[2].x = 0L;
             aptl[2].y = 0L;

             GpiBitBlt(global.pThrTop->hps, global.CutCopyPS, 3L, aptl, global
             WinInvalidateRect(global.hwnd, &(ti.rclTrack), FALSE);
         }
     }
 }




 /************************************************************************
 *
 *   LfSelectXform
 *
 *   Select a similarity transform.
 *
 ************************************************************************/

 VOID
 LfSelectXform(hwnd, pthr, id)
 HWND hwnd;
 PTHR pthr;
 USHORT id;
 {
     BOOL fAlreadySaidIt = FALSE;


     if ((id != pthr->usCurXform) ||
        ((id != global.usCurXform) && global.fUpdateAttrs))
     {
         if (global.fUpdateAttrs && (global.flMiscAttrs & LFA_CURXFORM))
         {
             MyMessageBox(hwnd, pszAttrs);
             fAlreadySaidIt = TRUE;
         }

         global.flMiscAttrs |= LFA_CURXFORM;

         if (LfIsThreadTop(pthr))
         {
             if (fAlreadySaidIt)
                 UNCHECK_MENU_ITEM(global.hwndFrame, global.usCurXform);
             else
                 UNCHECK_MENU_ITEM(global.hwndFrame, pthr->usCurXform);
             global.usCurXform = id;
             CHECK_MENU_ITEM(global.hwndFrame, id);
         }
         else
             global.usCurXform = id;

         pthr->fUpdateAttrs  = TRUE;
         global.fUpdateAttrs = TRUE;
     }

     if (pthr->fAutoSelectDims)
     {
         if (global.fUpdateAttrs && (global.flMiscAttrs & (LFA_XSCALE |
                  LFA_YSCALE | LFA_XOFF | LFA_YOFF | LFA_POLYGONSIDES)))
             if (!fAlreadySaidIt)
                 MyMessageBox(hwnd, pszAttrs);

         global.dblXScale      = aXform[id - IDM_SHARKTOOTH].xScale;
         global.dblYScale      = aXform[id - IDM_SHARKTOOTH].yScale;
         global.dblXOff              = aXform[id - IDM_SHARKTOOTH].xOff;
         global.dblYOff              = aXform[id - IDM_SHARKTOOTH].yOff;
         global.usPolygonSides = aXform[id - IDM_SHARKTOOTH].usSides;

         global.flMiscAttrs |= LFA_XSCALE | LFA_YSCALE | LFA_XOFF | LFA_YOFF |
                               LFA_POLYGONSIDES;
         pthr->fUpdateAttrs  = TRUE;
         global.fUpdateAttrs = TRUE;
     }
 }




 /************************************************************************
 *
 *   LfSelectPrim
 *
 *   Select a drawing primitive.
 *
 ************************************************************************/

 VOID
 LfSelectPrim(hwnd, pthr, id)
 HWND hwnd;
 PTHR pthr;
 USHORT id;
 {
     BOOL fAlreadySaidIt = FALSE;


     if ((id != pthr->usCurPrim) ||
        ((id != global.usCurPrim) && global.fUpdateAttrs))
     {
         if (global.fUpdateAttrs && (global.flMiscAttrs & LFA_CURPRIM))
         {
             MyMessageBox(hwnd, "Previous attributes not read yet.");
             fAlreadySaidIt = TRUE;
         }

         if (LfIsThreadTop(pthr))
         {
             if (fAlreadySaidIt)
                 UNCHECK_MENU_ITEM(global.hwndFrame, global.usCurPrim);
             else
                 UNCHECK_MENU_ITEM(global.hwndFrame, pthr->usCurPrim);
             global.usCurPrim = id;
             CHECK_MENU_ITEM(global.hwndFrame, id);
         }
         else
             global.usCurPrim = id;

         global.flMiscAttrs |= LFA_CURPRIM;
         pthr->fUpdateAttrs  = TRUE;
         global.fUpdateAttrs = TRUE;
     }
 }


 LFDLG.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFDLG.C

 /************************************************************************
 *
 *   lfdlg.c -- Window procedures for dialog boxes used in LineFractal.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFDLG
 #define INCL_LFUTIL
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 *   global        Global data block.
 *
 ************************************************************************/

 extern GLOBALDATA global;




 /************************************************************************
 *
 *   AboutDlg
 *
 *   Process messages for the About box.
 *
 ************************************************************************/

 ULONG CALLBACK
 AboutDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (usMsg)
     {
     case WM_COMMAND:                        /* the user has pressed a button
         switch (SHORT1FROMMP(mp1))        /* which button? */
         {
         case IDD_OK:
         case IDD_CANCEL:
             WinDismissDlg(hwnd, TRUE);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   TimerDelayDlg
 *
 *   Process messages for the dialog box used to set the timer delay
 *   values for automatically restarting a drawing.
 *
 ************************************************************************/

 ULONG CALLBACK
 TimerDelayDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:
         MySetWindowUShort(hwnd, IDD_MINDELAY, global.usMinTimerDelay);
         MySetWindowUShort(hwnd, IDD_MAXDELAY, global.usMaxTimerDelay);
         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:
             MyGetWindowUShort(hwnd, IDD_MINDELAY, &global.usMinTimerDelay);
             MyGetWindowUShort(hwnd, IDD_MAXDELAY, &global.usMaxTimerDelay);

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   BitmapParamsDlg
 *
 *   Process messages for the bitmap parameters dialog box.
 *
 ************************************************************************/

 ULONG CALLBACK
 BitmapParamsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         MySetWindowUShort(hwnd, IDD_BMCX,     global.bm.cx);
         MySetWindowUShort(hwnd, IDD_BMCY,     global.bm.cy);
         MySetWindowUShort(hwnd, IDD_BMPLANES, global.bm.cPlanes);
         MySetWindowUShort(hwnd, IDD_BMBPP,    global.bm.cBitCount);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             MyGetWindowUShort(hwnd, IDD_BMCX,          &global.bm.cx);
             MyGetWindowUShort(hwnd, IDD_BMCY,          &global.bm.cy);
             MyGetWindowUShort(hwnd, IDD_BMPLANES, &global.bm.cPlanes);
             MyGetWindowUShort(hwnd, IDD_BMBPP,          &global.bm.cBitCount)

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   SwitchDelayDlg
 *
 *   Process messages for the dialog box used to set the switch
 *   delay value for automatically switching between threads.
 *
 ************************************************************************/

 ULONG CALLBACK
 SwitchDelayDlg(hwnd, usMsg, mp1, mp2)
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:
         MySetWindowUShort(hwnd, IDD_SWITCHDELAY, global.usSwitchDelay);
         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:
             MyGetWindowUShort(hwnd, IDD_SWITCHDELAY, &global.usSwitchDelay);

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   LineAttrsDlg
 *
 *   Process messages for the line attributes dialog box.
 *
 ************************************************************************/

 #define SETLINEATTR(H, I, J)
         MySetWindow##I(hwnd, J, global.lb.H)

 #define GETLINEATTR(H, I, J, K, L)                                          \
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.lb.H)
             {
                 global.lb.H = temp;
                 global.flLineAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 LineAttrsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flLineAttrs & LFA_LINEALL))
             global.lb = global.pThrTop->lb;

         SETLINEATTR(lColor,        Long,        IDD_LINECOLOR);
         SETLINEATTR(usMixMode,        UShort, IDD_LINEMIX);
         SETLINEATTR(fxWidth,        Long,        IDD_LINEWIDTH);
         SETLINEATTR(lGeomWidth, Long,        IDD_LINEGEOMWIDTH);
         SETLINEATTR(usType,        UShort, IDD_LINETYPE);
         SETLINEATTR(usEnd,        UShort, IDD_LINEEND);
         SETLINEATTR(usJoin,        UShort, IDD_LINEJOIN);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETLINEATTR(lColor,     Long,   LONG,   IDD_LINECOLOR,     LFA_LI
             GETLINEATTR(usMixMode,  UShort, USHORT, IDD_LINEMIX,       LFA_LI
             GETLINEATTR(fxWidth,    Long,   LONG,   IDD_LINEWIDTH,     LFA_LI
             GETLINEATTR(lGeomWidth, Long,   LONG,   IDD_LINEGEOMWIDTH, LFA_LI
             GETLINEATTR(usType,     UShort, USHORT, IDD_LINETYPE,      LFA_LI
             GETLINEATTR(usEnd,            UShort, USHORT, IDD_LINEEND,
             GETLINEATTR(usJoin,     UShort, USHORT, IDD_LINEJOIN,      LFA_LI

             if (global.flLineAttrs & LFA_LINEALL)
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETLINEATTR
 #undef GETLINEATTR




 /************************************************************************
 *
 *   MarkerAttrsDlg
 *
 *   Process messages for the marker attributes dialog box.
 *
 ************************************************************************/

 #define SETMARKATTR(H, I, J)
         MySetWindow##I(hwnd, J, global.mb.H)

 #define GETMARKATTR(H, I, J, K, L)                                          \
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.mb.H)
             {
                 global.mb.H = temp;
                 global.flMarkerAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 MarkerAttrsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flMarkerAttrs & LFA_MARKALL))
             global.mb = global.pThrTop->mb;

         SETMARKATTR(lColor,           Long,   IDD_MARKCOLOR);
         SETMARKATTR(lBackColor,    Long,   IDD_MARKBACKCOLOR);
         SETMARKATTR(usMixMode,           UShort, IDD_MARKMIX);
         SETMARKATTR(usBackMixMode, UShort, IDD_MARKBACKMIX);
         SETMARKATTR(usSet,           UShort, IDD_MARKSET);
         SETMARKATTR(usSymbol,           UShort, IDD_MARKSYMBOL);
         SETMARKATTR(sizfxCell.cx,  Long,   IDD_MARKCELLWIDTH);
         SETMARKATTR(sizfxCell.cy,  Long,   IDD_MARKCELLHEIGHT);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETMARKATTR(lColor,        Long,   LONG,   IDD_MARKCOLOR,
             GETMARKATTR(lBackColor,    Long,   LONG,   IDD_MARKBACKCOLOR,  LF
             GETMARKATTR(usMixMode,     UShort, USHORT, IDD_MARKMIX,
             GETMARKATTR(usBackMixMode, UShort, USHORT, IDD_MARKBACKMIX,
             GETMARKATTR(usSet,               UShort, USHORT, IDD_MARKSET,
             GETMARKATTR(usSymbol,      UShort, USHORT, IDD_MARKSYMBOL,
             GETMARKATTR(sizfxCell.cx,  Long,   LONG,   IDD_MARKCELLWIDTH,  LF
             GETMARKATTR(sizfxCell.cy,  Long,   LONG,   IDD_MARKCELLHEIGHT, LF

             if (global.flMarkerAttrs & LFA_MARKALL)
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETMARKATTR
 #undef GETMARKATTR




 /************************************************************************
 *
 *   AreaAttrsDlg
 *
 *   Process messages for the area attributes dialog box.
 *
 ************************************************************************/

 #define SETAREAATTR(H, I, J)
         MySetWindow##I(hwnd, J, global.ab.H)

 #define GETAREAATTR(H, I, J, K, L)                                          \
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.ab.H)
             {
                 global.ab.H = temp;
                 global.flAreaAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 AreaAttrsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flAreaAttrs & LFA_AREAALL))
             global.ab = global.pThrTop->ab;

         SETAREAATTR(lColor,           Long,   IDD_AREACOLOR);
         SETAREAATTR(lBackColor,    Long,   IDD_AREABACKCOLOR);
         SETAREAATTR(usMixMode,           UShort, IDD_AREAMIX);
         SETAREAATTR(usBackMixMode, UShort, IDD_AREABACKMIX);
         SETAREAATTR(usSet,           UShort, IDD_AREASET);
         SETAREAATTR(usSymbol,           UShort, IDD_AREASYMBOL);
         SETAREAATTR(ptlRefPoint.x, Long,   IDD_AREAREFX);
         SETAREAATTR(ptlRefPoint.y, Long,   IDD_AREAREFY);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETAREAATTR(lColor,        Long,   LONG,   IDD_AREACOLOR,
             GETAREAATTR(lBackColor,    Long,   LONG,   IDD_AREABACKCOLOR, LFA
             GETAREAATTR(usMixMode,     UShort, USHORT, IDD_AREAMIX,         L
             GETAREAATTR(usBackMixMode, UShort, USHORT, IDD_AREABACKMIX,
             GETAREAATTR(usSet,               UShort, USHORT, IDD_AREASET,
             GETAREAATTR(usSymbol,      UShort, USHORT, IDD_AREASYMBOL,
             GETAREAATTR(ptlRefPoint.x, Long,   LONG,   IDD_AREAREFX,
             GETAREAATTR(ptlRefPoint.y, Long,   LONG,   IDD_AREAREFY,

             if (global.flAreaAttrs & LFA_AREAALL)
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETAREAATTR
 #undef GETAREAATTR




 /************************************************************************
 *
 *   ImageAttrsDlg
 *
 *   Process messages for the image attributes dialog box.
 *
 ************************************************************************/

 #define SETIMAGEATTR(H, I, J)
         MySetWindow##I(hwnd, J, global.ib.H)

 #define GETIMAGEATTR(H, I, J, K, L)
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.ib.H)
             {
                 global.ib.H = temp;
                 global.flImageAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 ImageAttrsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flImageAttrs & LFA_IMAGEALL))
             global.ib = global.pThrTop->ib;

         SETIMAGEATTR(lColor,            Long,   IDD_IMAGECOLOR);
         SETIMAGEATTR(lBackColor,    Long,   IDD_IMAGEBACKCOLOR);
         SETIMAGEATTR(usMixMode,     UShort, IDD_IMAGEMIX);
         SETIMAGEATTR(usBackMixMode, UShort, IDD_IMAGEBACKMIX);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETIMAGEATTR(lColor,        Long,        LONG,        IDD_IMAGECO
             GETIMAGEATTR(lBackColor,        Long,        LONG,        IDD_IMA
             GETIMAGEATTR(usMixMode,        UShort, USHORT, IDD_IMAGEMIX,
             GETIMAGEATTR(usBackMixMode, UShort, USHORT, IDD_IMAGEBACKMIX,   L

             if (global.flImageAttrs & LFA_IMAGEALL)
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETIMAGEATTR
 #undef GETIMAGEATTR




 /************************************************************************
 *
 *   MiscAttrsDlg
 *
 *   Process messages for the fractal attributes dialog box.
 *
 ************************************************************************/

 #define SETMISCATTR(H, I, J)
         MySetWindow##I(hwnd, J, global.H)

 #define GETMISCATTR(H, I, J, K, L)                                          \
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.H)                                          \
             {
                 global.H = temp;                                          \
                 global.flMiscAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 MiscAttrsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flMiscAttrs &
                 (LFA_RECURSION|LFA_CPTMAX|LFA_POLYGONSIDES|LFA_ROTATION)))
         {
             global.usRecursion          = global.pThrTop->usRecursion;
             global.cptMax          = global.pThrTop->cptMax;
             global.usPolygonSides = global.pThrTop->usPolygonSides;
             global.dblRotation          = global.pThrTop->dblRotation;
         }

         SETMISCATTR(usRecursion,    UShort, IDD_RECURSION);
         SETMISCATTR(cptMax,            UShort, IDD_CPTMAX);
         SETMISCATTR(usPolygonSides, UShort, IDD_NUMSIDES);
         SETMISCATTR(dblRotation,    Double, IDD_ROTATION);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETMISCATTR(usRecursion,        UShort, USHORT, IDD_RECURSION, LF
             GETMISCATTR(cptMax,         UShort, USHORT, IDD_CPTMAX,    LFA_CP
             GETMISCATTR(usPolygonSides, UShort, USHORT, IDD_NUMSIDES,  LFA_PO
             GETMISCATTR(dblRotation,        Double, double, IDD_ROTATION,  LF

             if (global.flMiscAttrs & LFA_POLYGONSIDES)
                 if (global.usPolygonSides == 0)
                     global.usPolygonSides = 2;

             if (global.flMiscAttrs & LFA_CPTMAX)
             {
                 if (global.cptMax == 0)
                     global.cptMax = 1;
                 else if (global.cptMax > MAX_POINT_COUNT)
                     global.cptMax = MAX_POINT_COUNT;
             }

             if (global.flMiscAttrs & (LFA_RECURSION|LFA_CPTMAX|
                 LFA_POLYGONSIDES|LFA_ROTATION))
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETMISCATTR
 #undef GETMISCATTR




 /************************************************************************
 *
 *   DimensionsDlg
 *
 *   Process messages for the fractal dimensions dialog box.
 *
 ************************************************************************/

 #define SETDIMENSIONS(H, I, J)
         MySetWindow##I(hwnd, J, global.H)

 #define GETDIMENSIONS(H, I, J, K, L)
         {                                                                  \
             J temp;
             MyGetWindow##I(hwnd, K, &temp);
             if (temp != global.H)                                          \
             {
                 global.H = temp;                                          \
                 global.flMiscAttrs |= L;                                  \
             }
         }

 ULONG CALLBACK
 DimensionsDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         if (!global.fUpdateAttrs || !(global.flMiscAttrs &
             (LFA_XOFF|LFA_YOFF|LFA_XSCALE|LFA_YSCALE)))
         {
             global.dblXOff   = global.pThrTop->dblXOff;
             global.dblYOff   = global.pThrTop->dblYOff;
             global.dblXScale = global.pThrTop->dblXScale;
             global.dblYScale = global.pThrTop->dblYScale;
         }

         SETDIMENSIONS(dblXOff,         Double, IDD_XOFF);
         SETDIMENSIONS(dblYOff,         Double, IDD_YOFF);
         SETDIMENSIONS(dblXScale, Double, IDD_XSCALE);
         SETDIMENSIONS(dblYScale, Double, IDD_YSCALE);

         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETDIMENSIONS(dblXOff,   Double, double, IDD_XOFF,        LFA_XOF
             GETDIMENSIONS(dblYOff,   Double, double, IDD_YOFF,        LFA_YOF
             GETDIMENSIONS(dblXScale, Double, double, IDD_XSCALE, LFA_XSCALE);
             GETDIMENSIONS(dblYScale, Double, double, IDD_YSCALE, LFA_YSCALE);

             if (global.flMiscAttrs & (LFA_XOFF|LFA_YOFF|LFA_XSCALE|LFA_YSCALE
             {
                 global.pThrTop->fUpdateAttrs = TRUE;
                 global.fUpdateAttrs = TRUE;
             }

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETDIMENSIONS
 #undef GETDIMENSIONS




 /************************************************************************
 *
 *   PtrPreferencesDlg
 *
 *   Process messages for the mouse pointer preferences dialog box.
 *
 ************************************************************************/

 #define SETPTRPREF(H, I, J)                                    \
         MySetWindow##I(hwnd, J, global.H)

 #define GETPTRPREF(H, I, J)                                    \
         MyGetWindow##I(hwnd, J, &global.H)

 ULONG CALLBACK
 PtrPreferencesDlg( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL fRet = FALSE;

     switch (usMsg)
     {
     case WM_INITDLG:

         SETPTRPREF(usPtrThreshold, UShort, IDD_PTRTHRESHOLD);
         WinSendDlgItemMsg(hwnd, global.usUserPtr+IDD_USERPTR0,
                           BM_SETCHECK, (MPARAM)TRUE, 0L);
         return FALSE;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1))
         {
         case IDD_OK:

             GETPTRPREF(usPtrThreshold, UShort, IDD_PTRTHRESHOLD);
             global.usUserPtr = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_USER
                                                   BM_QUERYCHECKINDEX, 0L, 0L)

 /*            if (global.usPtrThreshold < 0)
                 global.usPtrThreshold = 0; */

             if (!global.fAnimatePtr)
                 global.usCurPtr = global.usUserPtr;

             fRet = TRUE;

             /* fall through to some common code */

         case IDD_CANCEL:
             WinDismissDlg(hwnd, fRet);
             break;

         default:
             return FALSE;
         }
         break;

     default:
         return (ULONG) WinDefDlgProc(hwnd, usMsg, mp1, mp2);
     }
     return FALSE;
 }
 #undef SETPTRPREF
 #undef GETPTRPREF


 LFDRAW.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFDRAW.C

 /************************************************************************
 *
 *   lfdraw.c -- This file contains "line fractal" drawing routines
 *
 *   Created by Microsoft Corporation, 1989
 *
 *
 *   Background fractal drawing scheme:
 *
 *        To enable the user to interact with the system during
 *        the drawing of a complicated fractal, the fractal is
 *        drawn into a bitmap by a background thread.  This thread
 *        is started at the first WM_PAINT message received by the
 *        application, and never terminates.
 *
 *        The thread's execution is controlled by a semaphore,
 *        lSemRedraw.  The thread is initially blocked
 *        by its semaphore.  When something changes in the environ-
 *        ment such that the fractal is to be redrawn, or drawn for
 *        the first time, the semaphore is cleared and the thread
 *        is off and running.  Note that the environment must be
 *        set up BEFORE the semaphore is cleared, otherwise the
 *        fractal may be partially drawn with old parameters.
 *        The thread automatically resets the semaphore, so that
 *        as soon as it's done drawing, it has to wait for the signal
 *        to start again.
 *
 *        The fractal is drawn in batches of up to 8196 points per polyline.
 *        After each polyline is drawn, the background thread invalidates
 *        the main client rectangle to force a WM_PAINT message to be sent.
 *        All the paint procedure does is copy the bitmap, whatever
 *        it's current state, to the screen.  The user therefore
 *        sees bursts of n points at a time as his fractal is drawn.
 *
 *        The semaphore is controlled, in greater detail, as follows:
 *
 *            Disable background drawing (set semaphore):
 *
 *                LfInit
 *                    Don't let second thread start working until a
 *                    transform has been defined.
 *
 *                LineFractalThread
 *                    Don't start the next one until the user asks for it.
 *
 *            Enable background drawing (clear semaphore):
 *
 *                WM_BUTTON1UP
 *                WM_BUTTON2UP
 *                WM_SIZE & fAutoSizePS
 *                    The level of recursion or dimensions of bitmap have
 *                    changed, so redraw the fractal.
 *
 *                Change of fractal
 *                Change of drawing primitive
 *                Change of attributes
 *                    If the corresponding redraw flag is enabled for one
 *                    of these events, then the semaphore is cleared.
 *
 *
 *         Event                                          LineFractalThread
 *         -----                                          -----------------
 *
 *    WM_BUTTON1UP                                  --------<--------
 *    WM_BUTTON2UP                                 /                   \
 *    WM_SIZE & fAutoSizePS                        |                    |
 *    Change of fractal/primitive/attributes        |                    |
 *           |                                        |                    |
 *           |                                        V                    |
 *           +---------clear----------> +----------------------+            |
 *                                      |     lSemRedraw             |
 *           +----------set-----------> +----------------------+            |
 *           |                                        |                    |
 *           |                              if semaphore is clear            ^
 *    initialization                                |                    |
 * done with current fractal                        V                    |
 *                                      +----------------------+            |
 *                                      |    Draw fractal      |            |
 *                       WM_PAINT <---- |    into bitmap             |
 *                                      +----------------------+            |
 *                                                |                    |
 *                                                |                    |
 *                                                 \                    /
 *                                                  -------->--------
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSPROCESS

 #include <os2.h>
 #include <math.h>

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFTHREAD
 #define INCL_LFDRAW
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 extern GLOBALDATA global;
 extern XFORMDATA  aXform[];




 /************************************************************************
 *
 *   LineFractalThread
 *
 *   Organize the drawing of the fractal.  Runs in an independent
 *   thread to accumulate the points of the fractal, then calls
 *   LfDraw to draw with the selected primitive onto the surface in
 *   batches of a size selected by the user.  If this thread is the
 *   top, then all or part of the client rectangle is invalidated
 *   after drawing to force a WM_PAINT message.        When the paint message
 *   is processed, the image will be copied to the display.
 *
 *   This function is entered via _beginthread, which takes care of
 *   putting the parameter on the stack.
 *
 ************************************************************************/

 VOID FAR cdecl
 LineFractalThread(pthr)
 PTHR pthr;
 {
     HAB hab;
     int cFracSegs;
     ULONG cptReq;
     BOOL fCached;
     BOOL fCacheable;
     BOOL fModelXformsValid;
     PLINEFRAC pXform;               /* linked list of fractal segments */


     hab = WinInitialize(0);              /* initialize ring 2 stack for threa

     fModelXformsValid = FALSE;
     fCached              = FALSE;
     fCacheable              = FALSE;

     pthr->pptl              = NULL;
     pthr->pmatlf      = NULL;

     while (!pthr->fTimeToDie)
     {
         /****************************************************************
         *
         *  Clear the busy flag to indicate we're at the semaphore.
         *  If we happen to be the top thread, then force the pointer
         *  to be what we expect to see (if we didn't do this, it
         *  might stay as an hour glass until the user moves the mouse).
         *
         ****************************************************************/

         pthr->fBusy = FALSE;
         if (LfIsThreadTop(pthr))
             if ((global.hptr)[global.usCurPtr])
                 WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);


         /****************************************************************
         *
         *  Wait for permission to redraw.
         *  See if we're supposed to exit.  If not, clear the suicide
         *  flag, and set the busy flag.  If we're the top thread, then
         *  set the pointer to an hour glass to let the user know we're
         *  working on something.
         *
         ****************************************************************/

         DosSemRequest(&pthr->lSemRedraw, -1L);

         if (pthr->fTimeToDie)
             goto lfthread_exit;
         pthr->fInterrupted = FALSE;
         pthr->fBusy           = TRUE;
         if (LfIsThreadTop(pthr))
             if (global.hptrWait)
                 WinSetPointer(HWND_DESKTOP,global.hptrWait);

         /****************************************************************
         *
         *  Check for changes of attributes.  If anything has changed,
         *  this subroutine copies the new stuff over within a critical
         *  section.
         *
         ****************************************************************/

         LfUpdateAttrs(pthr);


         /****************************************************************
         *
         *  Check the buffers for points and model transforms.  If we
         *  don't have them, or the appropriate attributes have changed
         *  such that the ones we have are invalid, allocate for them.
         *
         ****************************************************************/

         if ((pthr->pptl == NULL) || (pthr->flMiscAttrs & LFA_CPTMAX))
         {
             if (pthr->pptl != NULL)
                 DosFreeSeg(*(((PUSHORT)&pthr->pptl)+1));
             if (DosAllocSeg(pthr->cptMax * sizeof(POINTL),
                            ((PUSHORT)&pthr->pptl)+1, 0))
                 goto lfthread_exit;
         }

         if ((pthr->pmatlf == NULL) || (pthr->flMiscAttrs & LFA_POLYGONSIDES))
         {
             if (pthr->pmatlf != NULL)
                 DosFreeSeg(*(((PUSHORT)&pthr->pmatlf)+1));
             if (DosAllocSeg(pthr->usPolygonSides * sizeof(MATRIXLF),
                            ((PUSHORT)&pthr->pmatlf)+1, 0))
                 goto lfthread_exit;
             fModelXformsValid = FALSE;
         }


         /****************************************************************
         *
         *  See if we can cache the whole lot of points.  This depends
         *  on the fractal, the size of the point cache, and the level
         *  of recursion.  Note that we must execute this code the first
         *  time through, or fCacheable will be undefined.  We're sure
         *  to come here, though, because threads are initialized with
         *  all attributes "changed".
         *
         ****************************************************************/

         if (pthr->flMiscAttrs & (LFA_CPTMAX | LFA_RECURSION | LFA_CURXFORM))
         {
             if (pthr->flMiscAttrs & LFA_CURXFORM)
             {
                 PLINEFRAC p;

                 cFracSegs = 0;
                 pXform = aXform[pthr->usCurXform - IDM_SHARKTOOTH].pXform;
                 p = pXform;
                 while (p != EOLIST)
                 {
                     ++cFracSegs;
                     p = p->next;
                 }
             }

             cptReq = (ULONG) exp((double)pthr->usRecursion *
                                  log((double)cFracSegs));

             if ((ULONG)pthr->cptMax > cptReq)
                 fCacheable = TRUE;
             else
                 fCacheable = FALSE;

             fCached = FALSE;
         }


         /****************************************************************
         *
         *  If the model transforms are invalid, then recompute them.
         *  Check first to see if any attributes have changed that
         *  would invalidate the transforms.
         *
         ****************************************************************/

         if (fModelXformsValid)
             if (pthr->flMiscAttrs & (LFA_ROTATION | LFA_POLYGONSIDES |
                    LFA_XSCALE | LFA_YSCALE | LFA_XOFF | LFA_YOFF))
                 fModelXformsValid = FALSE;

         if (!fModelXformsValid)
         {
             LfComputeModelXforms(pthr);
             fModelXformsValid = TRUE;
         }


         /****************************************************************
         *
         *  Clear the change-of-attributes flags that have been examined
         *  by the time we get here.
         *
         ****************************************************************/

         pthr->flMiscAttrs &=
           ~(
              LFA_CPTMAX    | LFA_RECURSION | LFA_POLYGONSIDES | LFA_CURXFORM
              LFA_XSCALE    | LFA_YSCALE    | LFA_XOFF              | LFA_YOFF
              LFA_ROTATION
            );


         /****************************************************************
         *
         *  Clear the surface if fClearOnRedraw is enabled.
         *  If the points are cached, then redraw straight from the cache.
         *  Otherwise, anchor the fractal at the left endpoint of the
         *  unit interval, then draw it to the specified depth of recursion.
         *  If we are able to cache all the points, then nothing will have
         *  been drawn when LineFractal returns, so draw the fractal without
         *  flushing the cache.        If we were not able to cache all the
         *  points, and the buffer is not empty, flush the last batch.
         *
         ****************************************************************/

         if (pthr->fClearOnRedraw)
             LfClearRect(pthr, NULL);

         if (fCached)
             LfDraw(pthr, FALSE);
         else
         {
             pthr->x    =  0.0;
             pthr->y    =  0.0;

             pthr->cptl = 0L;
             LfAddPoint(pthr);

             LineFractal(pthr, pthr->usRecursion, (double)pthr->cxWCS,
                         0.0, FALSE, pXform);

             if (!pthr->fInterrupted)
             {
                 if (fCacheable)
                 {
                     LfDraw(pthr, FALSE);
                     fCached = TRUE;
                 }
                 else
                 {
                     fCached = FALSE;
                     if (pthr->cptl > 1L)
                         LfDraw(pthr, TRUE);
                 }
             }
         }
     }


     /****************************************************************
     *
     *  Common exit point for thread.  Free up memory allocated
     *  by this thread.
     *
     ****************************************************************/

 lfthread_exit:

     if (pthr->pmatlf != NULL)
         DosFreeSeg(*(((PUSHORT)&pthr->pmatlf)+1));
     if (pthr->pptl != NULL)
         DosFreeSeg(*(((PUSHORT)&pthr->pptl)+1));
 }




 /************************************************************************
 *
 *   LfUpdateAttrs
 *
 *   Update any changed attributes from the global attributes.
 *
 ************************************************************************/

 VOID
 LfUpdateAttrs(pthr)
 PTHR pthr;
 {

     DosEnterCritSec();
     if (pthr->fUpdateAttrs)
     {
         if (global.flLineAttrs         & LFA_LINEALL)
         {
             pthr->lb                 = global.lb;
             pthr->flLineAttrs        |= global.flLineAttrs;
             global.flLineAttrs         = 0L;
         }
         if (global.flMarkerAttrs & LFA_MARKALL)
         {
             pthr->mb                 = global.mb;
             pthr->flMarkerAttrs |= global.flMarkerAttrs;
             global.flMarkerAttrs = 0L;
         }
         if (global.flAreaAttrs         & LFA_AREAALL)
         {
             pthr->ab                 = global.ab;
             pthr->flAreaAttrs        |= global.flAreaAttrs;
             global.flAreaAttrs         = 0L;
         }
         if (global.flImageAttrs  & LFA_IMAGEALL)
         {
             pthr->ib                 = global.ib;
             pthr->flImageAttrs        |= global.flImageAttrs;
             global.flImageAttrs  = 0L;
         }
         if (global.flMiscAttrs         & LFA_MISCALL)
         {
             if (global.flMiscAttrs & LFA_CURPRIM)
                 pthr->usCurPrim      = global.usCurPrim;
             if (global.flMiscAttrs & LFA_CURXFORM)
                 pthr->usCurXform     = global.usCurXform;
             if (global.flMiscAttrs & LFA_RECURSION)
                 pthr->usRecursion    = global.usRecursion;
             if (global.flMiscAttrs & LFA_POLYGONSIDES)
                 pthr->usPolygonSides = global.usPolygonSides;
             if (global.flMiscAttrs & LFA_CPTMAX)
                 pthr->cptMax             = global.cptMax;
             if (global.flMiscAttrs & LFA_XOFF)
                 pthr->dblXOff             = global.dblXOff;
             if (global.flMiscAttrs & LFA_YOFF)
                 pthr->dblYOff             = global.dblYOff;
             if (global.flMiscAttrs & LFA_XSCALE)
                 pthr->dblXScale      = global.dblXScale;
             if (global.flMiscAttrs & LFA_YSCALE)
                 pthr->dblYScale      = global.dblYScale;
             if (global.flMiscAttrs & LFA_ROTATION)
                 pthr->dblRotation    = global.dblRotation;
             if (global.flMiscAttrs & LFA_CXWCS)
                 pthr->cxWCS             = global.cxWCS;
             if (global.flMiscAttrs & LFA_CYWCS)
                 pthr->cyWCS             = global.cyWCS;
             pthr->flMiscAttrs  |= global.flMiscAttrs;
             global.flMiscAttrs        = 0L;
         }

         pthr->fUpdateAttrs  = FALSE;
         global.fUpdateAttrs = FALSE;

     }
     DosExitCritSec();


     /* Take care of the attribute bundles now. The miscellaneous attributes
      * require more processing, so don't clear their flags yet.
      */

     if (pthr->flLineAttrs & LFA_LINEALL)
     {
         GpiSetAttrs(pthr->hps, PRIM_LINE, pthr->flLineAttrs, 0L, &pthr->lb);
         pthr->flLineAttrs = 0L;
     }
     if (pthr->flMarkerAttrs & LFA_MARKALL)
     {
         GpiSetAttrs(pthr->hps, PRIM_MARKER, pthr->flMarkerAttrs, 0L, &pthr->m
         pthr->flMarkerAttrs = 0L;
     }
     if (pthr->flAreaAttrs & LFA_AREAALL)
     {
         GpiSetAttrs(pthr->hps, PRIM_AREA, pthr->flAreaAttrs, 0L, &pthr->ab);
         pthr->flAreaAttrs = 0L;
     }
     if (pthr->flImageAttrs)
     {
         GpiSetAttrs(pthr->hps, PRIM_IMAGE, pthr->flImageAttrs, 0L, &pthr->ib)
         pthr->flImageAttrs = 0L;
     }
 }




 /************************************************************************
 *
 *   LfComputeModelXforms
 *
 *   Compute the model transform matrices necessary to draw the fractal
 *   on each side of the polygonal frame.  The rotation, scaling, and
 *   translation are all rolled into one matrix for simplicity.
 *
 ************************************************************************/

 VOID
 LfComputeModelXforms(pthr)
 PTHR pthr;
 {
     double dblXScale, dblYScale;
     double dblXOff, dblYOff;
     double dblTheta;
     double dblRotation, dblSinRotation, dblCosRotation;
     double dblSideLen, dblAngleDecr;
     double dblXExtDims, dblYExtDims;
     double dblHalfXDims, dblHalfYDims;
     double dx, dy;
     PMATRIXLF pmatlf;
     USHORT i;


     dblAngleDecr = TWO_PI / (double)pthr->usPolygonSides;

     if (pthr->usPolygonSides == 1)
     {
         dblSideLen   = 1.0;
         dblRotation  = (double) pthr->dblRotation;
     }
     else
     {
         /* C 5.1 incorrectly compiles sin(temp_dbl2) in large
          * model, where temp_dbl2 is expanded to eliminate all
          * temporary variables, therefore I DO use the temp vars.
          */

         double temp_dbl1, temp_dbl2;

         temp_dbl1    = (double)pthr->usPolygonSides;
         temp_dbl2    = PI / temp_dbl1;
         dblSideLen   = sin(temp_dbl2);
         dblRotation  = PI - dblAngleDecr;
         dblRotation  = 0.5 * dblRotation + pthr->dblRotation;
     }

     {
         double temp_dbl1, temp_dbl2;

         temp_dbl1  = (double) pthr->rcl.xRight;
         temp_dbl2  = (double) pthr->cxWCS;
         dblXScale  = temp_dbl1 / temp_dbl2;
         dblXScale *= pthr->dblXScale * dblSideLen;

         temp_dbl1  = (double) pthr->rcl.yTop;
         temp_dbl2  = (double) pthr->cyWCS;
         dblYScale  = temp_dbl1 / temp_dbl2;
         dblYScale *= pthr->dblYScale * dblSideLen;
     }

     dblXExtDims  = (double) pthr->rcl.xRight * pthr->dblXScale;
     dblYExtDims  = (double) pthr->rcl.yTop   * pthr->dblYScale;
     dblHalfXDims = 0.5 * dblXExtDims;
     dblHalfYDims = 0.5 * dblYExtDims;
     dblXOff         = (double) pthr->rcl.xRight * pthr->dblXOff + dblHalfXDim
     dblYOff         = (double) pthr->rcl.yTop   * pthr->dblYOff + dblHalfYDim

     dblTheta         = PI + pthr->dblRotation;

     for (i = 0; i < pthr->usPolygonSides; ++i)
     {
         dblCosRotation = cos(dblRotation);
         dblSinRotation = sin(dblRotation);

         dx = dblHalfXDims * cos(dblTheta);
         dy = dblHalfYDims * sin(dblTheta);

         /* 0.000015 = about 1/65536 */

         pmatlf = pthr->pmatlf + i;
         pmatlf->fxM11 = (FIXED)(( dblCosRotation * dblXScale + 0.000015) * (d
         pmatlf->fxM12 = (FIXED)(( dblSinRotation * dblYScale + 0.000015) * (d
         pmatlf-> lM13 = 0L;
         pmatlf->fxM21 = (FIXED)((-dblSinRotation * dblXScale + 0.000015) * (d
         pmatlf->fxM22 = (FIXED)(( dblCosRotation * dblYScale + 0.000015) * (d
         pmatlf-> lM23 = 0L;
         pmatlf-> lM31 = (LONG) (dblXOff + dx + 0.5);
         pmatlf-> lM32 = (LONG) (dblYOff + dy + 0.5);
         pmatlf-> lM33 = 1L;

         dblRotation -= dblAngleDecr;
         dblTheta    -= dblAngleDecr;
     }
 }




 /************************************************************************
 *
 *   LineFractal
 *
 *   Draw fractal with the given similarity transform.
 *
 *   The general idea is to define a transformation to apply to the
 *   unit line segment, to get a new polyline.  This same transformation
 *   is then applied to each line segment of the new polyline.  The number
 *   of successive applications of the similarity transform is set by the
 *   user.  It's known as the level of recursion of the fractal.
 *
 *   Since this is where the point accumulation process will usually
 *   be, it recognizes the flag        fInterrupted  to allow the current
 *   work to be abandoned.
 *
 ************************************************************************/

 VOID
 LineFractal(pthr, depth, len, ang, flip, xform)
 PTHR pthr;
 int depth;
 double len;
 double ang;
 BOOL flip;
 PLINEFRAC xform;
 {
     double newlen;
     PLINEFRAC newseg;


     if (pthr->fInterrupted)
         return;

     if (depth)
     {
         /*
          *  We have not reached the maximum depth of recursion yet,
          *  so apply the similarity transform to the current line
          *  segment.
          */

         --depth;
         newseg = xform;
         do
         {
             newlen  = len * newseg->length;
             ang    += newseg->angle * (flip ? -1 : 1);
             LineFractal(pthr, depth, newlen, ang, (flip ^ newseg->flip), xfor
         } while ((newseg = newseg->next) != EOLIST);
     }
     else
     {
         /*
          *  We have reached the maximum depth of recursion, so
          *  draw a line segment.
          */

         pthr->x += len * cos(ang);
         pthr->y += len * sin(ang);
         LfAddPoint(pthr);
     }
 }




 /************************************************************************
 *
 *   LfAddPoint
 *
 *   Applies the global coordinate transform to the point (x, y), then
 *   stuffs it into the global PolyLine point array, and increments the
 *   count of points in the array.
 *
 ************************************************************************/

 VOID
 LfAddPoint(pthr)
 PTHR pthr;
 {
     if (pthr->cptl == (ULONG)pthr->cptMax)
         LfDraw(pthr, TRUE);

     if (pthr->cptl < (ULONG)pthr->cptMax)
     {
         (pthr->pptl + pthr->cptl)->x =
             (int)(pthr->x + 0.5);
         (pthr->pptl + pthr->cptl)->y =
             (int)(pthr->y + 0.5);
         ++pthr->cptl;
     }
 }




 /************************************************************************
 *
 *   LfDraw
 *
 *   For each segment of the frame, set the model transform and draw the
 *   cache of points in the current primitive.
 *
 *   Invalidate the client rectangle of the main window in case this is
 *   the top thread, to cause our latest bitmap to be copied there.
 *
 ************************************************************************/

 VOID
 LfDraw(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     USHORT i;
     BOOL myFlush;


     /* If this is a direct DC, but is not the top thread, then
      * don't draw anything.
      */
     if (pthr->dcType == IDM_DCDIRECT)
         if (!LfIsThreadTop(pthr))
         {
             if (fFlush)
                 pthr->cptl = 0L;
             return;
         }


     if (pthr->fCollectBounds)
         GpiResetBoundaryData(pthr->hps);


     myFlush = FALSE;

     for (i = 0; i < pthr->usPolygonSides; ++i)
     {
         if (pthr->fInterrupted)
             return;

         /* set model transform */
         GpiSetModelTransformMatrix(pthr->hps, 9L, pthr->pmatlf+i, TRANSFORM_R

         /* we only really flush the last time we use the cache */
         if (i == pthr->usPolygonSides - 1)
             myFlush = fFlush;

         switch ( pthr->usCurPrim )
         {
         case IDM_POLYLINE:
             LfDrawPolyLine(pthr, myFlush);
             break;

         case IDM_POLYFILLET:
             LfDrawPolyFillet(pthr, myFlush);
             break;

         case IDM_POLYSPLINE:
             LfDrawPolySpline(pthr, myFlush);
             break;

         case IDM_PEANO:
             LfDrawPolyPeano(pthr, myFlush);
             break;

         case IDM_POLYMARKER:
             LfDrawPolyMarker(pthr, myFlush);
             break;
         }

         if (pthr->dcType != IDM_DCDIRECT)
             if (LfIsThreadTop(pthr))
             {
                 if (pthr->fCollectBounds)
                 {
                     GpiQueryBoundaryData(pthr->hps, &pthr->rclBounds);
                     ++(pthr->rclBounds).xRight;
                     ++(pthr->rclBounds).yTop;
                     WinInvalidateRect(global.hwnd, &pthr->rclBounds, FALSE);
                 }
                 else
                     WinInvalidateRect(global.hwnd, &pthr->rcl, FALSE);
             }
     }
 }




 /************************************************************************
 *
 *   LfDrawPolyLine
 *
 *   Draw a polyline using the thread's point buffer.
 *
 ************************************************************************/

 VOID
 LfDrawPolyLine(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     /* After drawing the line, save the last point to set the
        current position before the next call. */

     GpiSetCurrentPosition( pthr->hps, pthr->pptl );
     GpiPolyLine( pthr->hps, pthr->cptl-1L, pthr->pptl+1 );
     if (fFlush)
     {
         *pthr->pptl = *(pthr->pptl + pthr->cptl-1);
         pthr->cptl = 1L;
     }
 }




 /************************************************************************
 *
 *   LfDrawPolyFillet
 *
 *   Draw a polyfillet using the thread's point buffer.
 *
 ************************************************************************/

 VOID
 LfDrawPolyFillet(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     /* After drawing the curve, save the last point to set the
        current position before the next call. */

     if (pthr->cptl > 2)
     {
         GpiSetCurrentPosition( pthr->hps, pthr->pptl );
         GpiPolyFillet( pthr->hps, pthr->cptl-1L, pthr->pptl+1 );
         if (fFlush)
         {
             *pthr->pptl = *(pthr->pptl + pthr->cptl-1);
             pthr->cptl = 1L;
         }
     }
 }




 /************************************************************************
 *
 *   LfDrawPolySpline
 *
 *   Draw a polyspline using the thread's point buffer.
 *
 ************************************************************************/

 VOID
 LfDrawPolySpline(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     USHORT i;                /* loop counter */
     USHORT cptSlack;        /* # points in pptl not usable by PolySpline */

     /* GpiPolySpline expects the number of points to be a
        multiple of 3.  If we have a non-multiple of three,
        (excluding the first point, which we've used to set
        the current position), only pass the largest multiple
        of three, saving the rest for the next go-round. */

     cptSlack = (int)((pthr->cptl-1L) % 3) + 1;
     GpiSetCurrentPosition( pthr->hps, pthr->pptl );
     GpiPolySpline( pthr->hps, pthr->cptl-cptSlack,
                    pthr->pptl+1 );
     if (fFlush)
     {
         for (i = 0; i < cptSlack; ++i)
             *(pthr->pptl + i) = *(pthr->pptl + pthr->cptl-cptSlack+i);
         pthr->cptl = cptSlack;
     }
 }




 /************************************************************************
 *
 *   LfDrawPolyPeano
 *
 *   Draw a chain of Peano primitives using the thread's point buffer.
 *
 ************************************************************************/

 VOID
 LfDrawPolyPeano(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     LONG a, b, c, d;        /* temporary vars for Peano curvelet calculations
     int cptPeano;        /* current point in pptl in use by Peano curve */
     POINTL ptPeano[2];        /* Peano curvelet array to pass to PolyLine */

     for (cptPeano = 0; cptPeano < (int)(pthr->cptl-1L); ++cptPeano)
     {
         ptPeano[0] = *(pthr->pptl + cptPeano);
         ptPeano[1] = *(pthr->pptl + cptPeano+1);
         a = (pthr->pptl + cptPeano+1)->x - (pthr->pptl + cptPeano)->x;
         b = (pthr->pptl + cptPeano+1)->y - (pthr->pptl + cptPeano)->y;
         c = (a + b)/2;
         d = (a - b)/2;
         if (labs(a) > labs(b))
         {
             ptPeano[0].x +=  d;
             ptPeano[0].y +=  c;
         }
         else
         {
             ptPeano[0].x +=  c;
             ptPeano[0].y += -d;
         }

         GpiSetCurrentPosition( pthr->hps, pthr->pptl + cptPeano);
         GpiPolyLine( pthr->hps, 2L, (PPOINTL)ptPeano);
     }
     if (fFlush)
     {
         *pthr->pptl = *(pthr->pptl + pthr->cptl-1);
         pthr->cptl = 1L;
     }
 }




 /************************************************************************
 *
 *   LfDrawPolyMarker
 *
 *   Draw a list of markers using the thread's point buffer.
 *
 ************************************************************************/

 VOID
 LfDrawPolyMarker(pthr, fFlush)
 PTHR pthr;
 BOOL fFlush;
 {
     /* I want to draw markers at every point in the array, but
        GpiPolyMarker won't draw at the last point!  So, GpiMarker
        does the job instead. */

     GpiSetCurrentPosition( pthr->hps, pthr->pptl + pthr->cptl-1);
     GpiPolyMarker( pthr->hps, pthr->cptl, pthr->pptl );
     GpiMarker         ( pthr->hps,                  pthr->pptl + pthr->cptl-1

     if (fFlush)
         pthr->cptl = 0L;
 }




 /************************************************************************
 *
 *   LfClearRect
 *
 *   Set the area attributes if needed and fill the bitmap with them.
 *
 ************************************************************************/

 VOID
 LfClearRect(pthr, prcl)
 PTHR pthr;
 PRECTL prcl;
 {
     PRECTL prclT;

     if (pthr->hps)
     {
         if (prcl)
             prclT = prcl;
         else
             prclT = &pthr->rcl;

         if (pthr->dcType == IDM_DCDIRECT)
         {
             /* If direct DC, only blt if top thread. */
             if (LfIsThreadTop(pthr))
                 GpiBitBlt(pthr->hps, NULL, 2L, (PPOINTL)prclT, ROP_PATCOPY, 0
         }
         else
         {
             GpiBitBlt(pthr->hps, NULL, 2L, (PPOINTL)prclT, ROP_PATCOPY, 0L);
             if (LfIsThreadTop(pthr))
                 WinInvalidateRect(global.hwnd, prclT, FALSE);
         }
     }
 }


 LFFILE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFFILE.C

 /************************************************************************
 *
 *   lffile.c -- File handling subroutines for LineFrac.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOSMEMMGR
 #define INCL_DOSFILEMGR
 #define INCL_BITMAPFILEFORMAT
 #include <os2.h>

 #include <stdio.h>

 #undef  RC_INVOKED
 #include "opendlg.h"

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFFILE
 #define INCL_LFPS
 #define INCL_LFUTIL
 #include "lffuncs.h"




 /*
  * this is the bitmap resource file format structure
  */
 typedef struct {
     USHORT    wType;
     ULONG     dwSize;
     int       xHotspot;
     int       yHotspot;
     ULONG     dwBitsOffset;
     USHORT    bmWidth;       /* from here this is a BitmapInfo table */
     USHORT    bmHeight;
     USHORT    bmPlanes;
     USHORT    bmBitcount;
 } RCBITMAP;
 typedef RCBITMAP FAR *PRCBITMAP;




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 extern GLOBALDATA global;




 /************************************************************************
 *
 *   LfReadFile
 *
 *   Calls the OpenDlg function to ask the user what file name to
 *   read from.
 *
 ************************************************************************/

 VOID
 LfReadFile(hwnd, pthr)
 HWND hwnd;
 PTHR pthr;
 {
     HFILE hFile;
     DLF dlf;

     dlf.rgbAction        = DLG_OPENDLG;
     dlf.rgbFlags        = ATTRDIRLIST;
     dlf.phFile                = &hFile;
     dlf.pszExt                = (PSZ)"\\*.bmp";
     dlf.pszAppName        = "LineFrac";
     dlf.pszTitle        = "Load Bitmap";
     dlf.pszInstructions = NULL;
     dlf.szFileName[0]        = '\0';
     dlf.szOpenFile[0]        = '\0';

     switch (DlgFile(hwnd,&dlf))
     {
     case TDF_ERRMEM:
     case TDF_INVALID:
         MyMessageBox(hwnd, "Error reading file.");
         break;

     case TDF_NOOPEN:
         break;

     default:
         if (!LfReadBMP(pthr, &dlf))
             MyMessageBox(hwnd, "Error reading file.\nIs AutoResize enabled?")
     }
 }




 /************************************************************************
 *
 *   LfReadBMP
 *
 *   Read a bitmap in from a BMP format file.  Prepare the DC for the
 *   given thread to accept it.        The user can have the DC resized to
 *   fit exactly the bitmap, or fit the bits in as best as we can.
 *   If we're not resizing and the bitmap is larger than the thread's
 *   DC, then load the bits flush with the lower left.
 *
 *   Both old-style (PRCBITMAP) and new-style (PBITMAPFILEHEADER)
 *   bitmaps can be read.
 *
 *   Free up memory and close the file before leaving.  The file
 *   will have been opened by the time this function is called,
 *   and the file handle will be in the *pdlf structure.
 *
 ************************************************************************/

 BOOL
 LfReadBMP(pthr, pdlf)
 PTHR pthr;
 PDLF pdlf;                /* File information filled by DlgFile. */
 {
     HFILE hfile;
     ULONG cScans;
     ULONG ulSize;         /* Number of bytes occupied by bitmap bits.
     USHORT cSegs;         /* Number of 64K segments in ulSize.
     USHORT cbExtra;         /* Bytes in last segment of ulSize.
     SEL sel;                 /* Base selector to file data.
     USHORT hugeshift;         /* Segment index shift value.
     USHORT cbRead1;         /* Number of bytes to read first call to DosRead
     USHORT cbRead2;         /* Number of bytes to read second call to DosRead
     USHORT cbRead;         /* Number of bytes read by DosRead.
     BOOL fRet = FALSE;         /* Function return code.
     USHORT i;                 /* Generic loop index.
     FILESTATUS fsts;
     PBITMAPFILEHEADER pbfh;
     PRCBITMAP  rb;
     PBYTE pImage;


     /*******************************************************************
     * Find out how big the file is so we can read the whole thing in.
     *******************************************************************/

     hfile = *(pdlf->phFile);
     if( DosQFileInfo( hfile, 1, &fsts, sizeof(FILESTATUS)) != 0)
         goto lfread_error_close_file;

     ulSize  = fsts.cbFile;
     cSegs   = (USHORT)(ulSize/0x10000L);
     cbExtra = (USHORT)(ulSize%0x10000L);
     if (DosAllocHuge(cSegs, cbExtra, (PSEL)&sel, 0, 0))
         goto lfread_error_close_file;
     if (DosGetHugeShift(&hugeshift))
         goto lfread_error_free_bits;

     pImage = (PBYTE)MAKEP(sel, 0);
     rb           = (PRCBITMAP)pImage;
     pbfh   = (PBITMAPFILEHEADER)pImage;


     /*******************************************************************
     * Read the bits in from the file. The DosRead function allows a
     * maximum of 64K-1 bytes read at a time.  We get around this
     * by reading two 32K chunks for each 64K segment, and reading the
     * last segment in one piece.
     *******************************************************************/

     for (i = 0; i <= cSegs; ++i)
     {
         if (i < cSegs)
         {
             /* This segment is 64K bytes long, so split it up. */
             cbRead1 = 0x8000;
             cbRead2 = 0x8000;
         }
         else
         {
             /* This segment is less than 64K bytes long, so read it all. */
             cbRead1 = cbExtra;
             cbRead2 = 0;
         }

         /* There's a possibility that cbExtra will be 0, so check
          * to avoid an unnecessary system call.
          */
         if (cbRead1 > 0)
         {
             if (DosRead( hfile
                        , (PVOID)MAKEP(sel+(i<<hugeshift), 0)
                        , cbRead1
                        , &cbRead))
                 goto lfread_error_free_bits;
             if (cbRead1 != cbRead)
                 goto lfread_error_free_bits;
         }

         /* This will always be skipped on the last partial segment. */
         if (cbRead2 > 0)
         {
             if (DosRead( hfile
                        , (PVOID)MAKEP(sel+(i<<hugeshift), cbRead1)
                        , cbRead2
                        , &cbRead))
                 goto lfread_error_free_bits;
             if (cbRead2 != cbRead)
                 goto lfread_error_free_bits;
         }
     }


     /*******************************************************************
     * At this point we have the bitmap completely in memory.  Now we
     * look at how the user wants them set into the thread's PS.  If
     * the thread has fAutoResizePS set, then make the PS fit the size
     * of the bitmap (the easy case).  If the flag is not set, then
     * figure out how to place it.
     *******************************************************************/

     if (pthr->fAutoSizePS)
     {
         if (pbfh->bmp.cbFix != sizeof(BITMAPINFOHEADER))
         {
             global.bm.cx        = rb->bmWidth;
             global.bm.cy        = rb->bmHeight;
             global.bm.cPlanes        = rb->bmPlanes;
             global.bm.cBitCount = rb->bmBitcount;
         }
         else
         {
             global.bm.cx        = pbfh->bmp.cx;
             global.bm.cy        = pbfh->bmp.cy;
             global.bm.cPlanes        = pbfh->bmp.cPlanes;
             global.bm.cBitCount = pbfh->bmp.cBitCount;
         }

         LfResizePS(pthr);
     }
     else
         goto lfread_error_free_bits;


     /*******************************************************************
     * Tell GPI to put the bits into the thread's PS. The function returns
     * the number of scan lines of the bitmap that were copied.        We want
     * all of them at once.
     *******************************************************************/

     if (pbfh->bmp.cbFix != sizeof(BITMAPINFOHEADER))
     {
         pImage += rb->dwBitsOffset;
         rb->dwBitsOffset = sizeof(BITMAPINFOHEADER);
         cScans = GpiSetBitmapBits( pthr->hps
                                  , 0L
                                  , (LONG)rb->bmHeight
                                  , pImage
                                  , (PBITMAPINFO)&(rb->dwBitsOffset));
         if (cScans != (ULONG)rb->bmHeight)  /* compare with original number o
             goto lfread_error_free_bits;
     }
     else
     {
         cScans = GpiSetBitmapBits( pthr->hps
                                  , 0L
                                  , (LONG)pbfh->bmp.cy
                                  , pImage + pbfh->offBits
                                  , (PBITMAPINFO)&(pbfh->bmp));
         if (cScans != (ULONG)pbfh->bmp.cy)  /* compare with original number o
             goto lfread_error_free_bits;
     }
     fRet = TRUE;     /* We made it!  The bits are in the thread's PS. */


     /*******************************************************************
     * Close the file, free the buffer space and leave.        This is a
     * common exit point from the function.  Since the same cleanup
     * operations need to be performed for such a large number of
     * possible error conditions, this is a concise way to do the right
     * thing.
     *******************************************************************/

 lfread_error_free_bits:
     DosFreeSeg(sel);
 lfread_error_close_file:
     DosClose(hfile);
     return fRet;
 }




 /************************************************************************
 *
 *   LfWriteFile
 *
 *   Calls the OpenDlg function to ask the user what file name to
 *   save under.
 *
 ************************************************************************/

 VOID
 LfWriteFile(hwnd, pthr)
 HWND hwnd;
 PTHR pthr;
 {
     HFILE hFile;
     DLF dlf;
     BITMAPINFOHEADER bmih;


     SetupDLF( &dlf
             , DLG_SAVEDLG
             , &hFile
             , (PSZ)"\\*.BMP"
             , (PSZ)"LineFrac"
             , (PSZ)"Save Bitmap"
             , NULL );
     dlf.szFileName[0] = '\0';
     dlf.szOpenFile[0] = '\0';

     switch (DlgFile(hwnd,&dlf))
     {
     case TDF_ERRMEM:
     case TDF_INVALID:
         MyMessageBox(hwnd, "Error opening file.");
         break;

     case TDF_NOSAVE:
         break;

     default:
         bmih.cbFix     = sizeof(BITMAPINFOHEADER);
         bmih.cx        = (USHORT) pthr->rcl.xRight;
         bmih.cy        = (USHORT) pthr->rcl.yTop;
         bmih.cPlanes   = pthr->cPlanes;
         bmih.cBitCount = pthr->cBitCount;

         if (!LfWriteBMP(pthr->hps, &bmih, &dlf))
             MyMessageBox(hwnd, "Error writing file.");
     }
 }




 /************************************************************************
 *
 *   LfWriteBMP
 *
 *   Write the bitmap out to a BMP format file.        Write the file
 *   header first, then the bitmap bits.  Space for the header
 *   and the bits is allocated.        Huge bitmaps are supported.
 *   Free up memory and close the file before leaving.  The file
 *   will have been opened by the time this function is called,
 *   and the file handle will be in the *pdlf structure.
 *
 ************************************************************************/

 BOOL
 LfWriteBMP(hPS, pbmih, pdlf)
 HPS hPS;                /* hPS from which to get bitmap bits.          */
 PBITMAPINFOHEADER pbmih;/* Bitmap information.                          */
 PDLF pdlf;                /* File information filled by DlgFile. */
 {
     HFILE hfile;
     ULONG cScans;
     ULONG ulSize;         /* Number of bytes occupied by bitmap bits.
     USHORT cSegs;         /* Number of 64K segments in ulSize.
     USHORT cbExtra;         /* Bytes in last segment of ulSize.
     SEL selBits;         /* Base selector to bitmap bits.
     USHORT hugeshift;         /* Segment index shift value.
     USHORT cbBMHdr;         /* Size of bitmap header.
     PBITMAPFILEHEADER pbfh; /* Pointer to private copy of bitmap info data.
     USHORT cbWrite1;         /* Number of bytes to write first call to DosWri
     USHORT cbWrite2;         /* Number of bytes to write second call to DosWr
     USHORT cbWritten;         /* Number of bytes written by DosWrite.
     BOOL fRet = FALSE;         /* Function return code.
     USHORT i;                 /* Generic loop index.
     struct
     {
         LONG cPlanes;
         LONG cBitCount;
     } bmFmt;


     hfile = *(pdlf->phFile);

     /*******************************************************************
     * If the bitmap was created with either 0 planes or 0 bits per
     * pixel, then query the format to write with.  By asking for just
     * one format (two LONGs, or one instance of structure of bmFmt),
     * we'll get the device's favored format.
     *******************************************************************/

     if ((pbmih->cPlanes == 0) || (pbmih->cBitCount))
     {
         if (!GpiQueryDeviceBitmapFormats(hPS, 2L, (PLONG)&bmFmt))
             goto lfwrite_error_close_file;
     }
     else
     {
         bmFmt.cPlanes        = pbmih->cPlanes;
         bmFmt.cBitCount = pbmih->cBitCount;
     }


     /*******************************************************************
     * Determine size of bitmap header.        The header consists of a
     * a fixed-size part and a variable-length color table.  The
     * latter has  2^cBitCount  entries, each of which is sizeof(RGB)
     * bytes long.  The exception is when cBitCount is 24, in which
     * case the color table is omitted because the pixels are direct
     * rgb values.
     *******************************************************************/

     i = (int) bmFmt.cBitCount;
     if (i == 24)
         cbBMHdr = 0;
     else
         for (cbBMHdr = sizeof(RGB); i > 0; --i)
             cbBMHdr *= 2;
     cbBMHdr += sizeof(BITMAPFILEHEADER);


     /*******************************************************************
     * Copy structure from input to work buffer.  The call to
     * GpiQueryBitmapBits will have write-access to this, so we won't
     * let it have the user's data.
     *******************************************************************/

     pbfh = 0;
     if (DosAllocSeg(cbBMHdr, ((PUSHORT)&pbfh)+1, 0))
         goto lfwrite_error_close_file;
     pbfh->bmp = *pbmih;
     if ((pbmih->cPlanes == 0) || (pbmih->cBitCount))
     {
         pbfh->bmp.cPlanes   = (USHORT) bmFmt.cPlanes;
         pbfh->bmp.cBitCount = (USHORT) bmFmt.cBitCount;
     }


     /*******************************************************************
     * Allocate space for the bitmap bits -- all of them at once.
     * The extra ULONG casts are there to force all the arithmetic
     * to be done in 32 bits.
     *******************************************************************/

     ulSize = (
                (
                  (
                    (ULONG)pbfh->bmp.cBitCount
                    * (ULONG)pbfh->bmp.cx
                    + 31L
                  ) / 32L
                ) * (ULONG)pbfh->bmp.cPlanes * 4L
              ) * (ULONG)pbfh->bmp.cy;

     cSegs   = (USHORT)(ulSize/0x10000L);
     cbExtra = (USHORT)(ulSize%0x10000L);
     if (DosAllocHuge(cSegs, cbExtra, (PSEL)&selBits, 0, 0))
         goto lfwrite_error_free_header;
     if (DosGetHugeShift(&hugeshift))
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Tell GPI to give us the bits. The function returns the number
     * of scan lines of the bitmap that were copied.  We want all of
     * them at once.
     *******************************************************************/

     cScans = GpiQueryBitmapBits( hPS
                                , 0L
                                , (ULONG)pbfh->bmp.cy
                                , (PBYTE)MAKEP(selBits, 0)
                                , (PBITMAPINFO)&pbfh->bmp);
     if (cScans != pbfh->bmp.cy)  /* compare with original number of scans */
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Fill in the extra header fields and write the header out to
     * the file.
     *******************************************************************/

     pbfh->usType    = 0x4D42;                  /* 'MB' */
     pbfh->cbSize    = ulSize + cbBMHdr;
     pbfh->xHotspot  = pbfh->bmp.cx / 2;    /* why bother ? */
     pbfh->yHotspot  = pbfh->bmp.cy / 2;
     pbfh->offBits   = cbBMHdr;

     if (DosWrite( hfile
                 , (PVOID)pbfh
                 , cbBMHdr
                 , &cbWritten))
         goto lfwrite_error_free_bits;
     if (cbWritten != cbBMHdr)
         goto lfwrite_error_free_bits;


     /*******************************************************************
     * Write the bits out to the file. The DosWrite function allows a
     * maximum of 64K-1 bytes written at a time.  We get around this
     * by writing two 32K chunks for each 64K segment, and writing the
     * last segment in one piece.
     *******************************************************************/

     for (i = 0; i <= cSegs; ++i)
     {
         if (i < cSegs)
         {
             /* This segment is 64K bytes long, so split it up. */
             cbWrite1 = 0x8000;
             cbWrite2 = 0x8000;
         }
         else
         {
             /* This segment is less than 64K bytes long, so write it all. */
             cbWrite1 = cbExtra;
             cbWrite2 = 0;
         }

         /* There's a possibility that cbExtra will be 0, so check
          * to avoid an unnecessary system call.
          */
         if (cbWrite1 > 0)
         {
             if (DosWrite( hfile
                         , (PVOID)MAKEP(selBits+(i<<hugeshift), 0)
                         , cbWrite1
                         , &cbWritten))
                 goto lfwrite_error_free_bits;
             if (cbWrite1 != cbWritten)
                 goto lfwrite_error_free_bits;
         }

         /* This will always be skipped on the last partial segment. */
         if (cbWrite2 > 0)
         {
             if (DosWrite( hfile
                         , (PVOID)MAKEP(selBits+(i<<hugeshift), cbWrite1)
                         , cbWrite2
                         , &cbWritten))
                 goto lfwrite_error_free_bits;
             if (cbWrite2 != cbWritten)
                 goto lfwrite_error_free_bits;
         }
     }

     fRet = TRUE;     /* We made it!  The bits are on the disk. */


     /*******************************************************************
     * Close the file, free the buffer space and leave.        This is a
     * common exit point from the function.  Since the same cleanup
     * operations need to be performed for such a large number of
     * possible error conditions, this is concise way to do the right
     * thing.
     *******************************************************************/

 lfwrite_error_free_bits:
     DosFreeSeg(selBits);
 lfwrite_error_free_header:
     DosFreeSeg(*((PUSHORT)&pbfh+1));
 lfwrite_error_close_file:
     DosClose(hfile);
     return fRet;
 }


 LFINIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFINIT.C

 /****************************************************************************
 *
 *   lfinit.c -- Initialization portion of LineFractal Application.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ****************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 #define INCL_GLOBALS
 #include "linefrac.h"

 #define INCL_LFINIT
 #define INCL_LFMAIN
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 extern GLOBALDATA global;
 extern ULONG aulRops[];




 /****************************************************************************
 *
 *   LfInitApp
 *
 *   Register application window class and creates standard window.
 *
 ****************************************************************************/

 BOOL FAR
 LfInitApp(VOID)
 {
     char szTitle[24];
     ULONG ctldata;
     PID pid;
     TID tid;
     HSWITCH hsw;
     static SWCNTRL swctl = { 0, 0, 0, 0, 0, SWL_VISIBLE,
                              SWL_JUMPABLE, "LineFractal", 0 };


     /*  Register Application Window Class  */

     WinLoadString( global.hab, (HMODULE) NULL, IDS_TITLE, sizeof(szTitle), (P
     if ( !WinRegisterClass( global.hab, (PCH)szTitle, (PFNWP)LineFracWndProc,
             CS_SIZEREDRAW, 0 ))
         return FALSE;


     /* Load the window's default pointer. */
     if (!((global.hptr)[0]   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!((global.hptr)[1]   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!((global.hptr)[2]   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!((global.hptr)[3]   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!(global.hptrTrack   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!(global.hptrPaste   = WinLoadPointer( HWND_DESKTOP,(HMODULE) NULL, I
         return FALSE;
     if (!(global.hptrWait    = WinQuerySysPointer(HWND_DESKTOP,SPTR_WAIT,FALS
         return FALSE;



     /* Create a window instance of class "LineFractal" */

     ctldata = FCF_STANDARD &
      ~(ULONG)(FCF_ICON | FCF_ACCELTABLE | FCF_TASKLIST);

     if (global.hwndFrame = WinCreateStdWindow(
         HWND_DESKTOP,                   /* specify desktop as parent window
         WS_VISIBLE,                   /* window styles
         &ctldata,                   /* frame creation flags
         (PCH)szTitle,                   /* window class name
         (PCH)szTitle,                   /* name appearing in window caption
         0L,                           /*
         (HMODULE)NULL,                   /* use current executable module id
         IDR_LINEFRACTAL,           /* menu id
         (HWND FAR *)&global.hwnd   /* window handle
         ))
     {
         global.fFirstThread                  = TRUE;

         global.fAutoSwitch                  = FALSE;
         global.usSwitchDelay                  = 1 * 1000;          /* 1 sec *
         global.fThreadInheritance          = TRUE;
         global.fUpdateMenusOnThreadSwitch = TRUE;
         global.fTimerOn                   = FALSE;
         global.usMinTimerDelay                  = 1 * 1000;          /* 1 sec
         global.usMaxTimerDelay                  = 5 * 1000;          /* 5 sec

         global.usRopIndex                  = 0;
         global.ulPasteROP                  = aulRops[global.usRopIndex];
         global.usPtrIncr                  = 1;
         global.usPtrCounter                  = 1;
         global.usUserPtr                  = 2;
         global.usCurPtr                   = global.usUserPtr;
         global.usPtrThreshold                  = 5;
         global.fUseClipbrd                  = FALSE;
         global.fTracking                  = FALSE;
         global.fSelecting                  = FALSE;
         global.fPasting                   = FALSE;
         global.fMouseChangesRecursion          = TRUE;
         global.fAnimatePtr                  = TRUE;
         global.fTempSelect                  = FALSE;
         global.fShowSelectRc                  = FALSE;
         global.fHaveSelectRc                  = FALSE;
         global.fHaveCutCopyDC                  = FALSE;

         global.fUpdateAttrs                  = FALSE;
         global.flLineAttrs                  = 0L;
         global.flMarkerAttrs                  = 0L;
         global.flAreaAttrs                  = 0L;
         global.flImageAttrs                  = 0L;
         global.flMiscAttrs                  = 0L;


         WinQueryWindowRect(global.hwnd, &global.rcl);

         global.cThr = 0;        /* start with no active threads */
         global.pThrTop = NULL;        /* make sure this guy's invalid */


 #define INIT_MENU_ITEM(val, var)     \
         TOGGLE_MENU_ITEM(global.hwndFrame, (val), (var))

         /* Check boolean menu items if they are set to true. */

         INIT_MENU_ITEM(IDM_AUTOSWITCH,                 global.fAutoSwitch);
         INIT_MENU_ITEM(IDM_UPDATEMENUS,                 global.fUpdateMenusOn
         INIT_MENU_ITEM(IDM_THREADINHERITANCE,         global.fThreadInheritan
         INIT_MENU_ITEM(IDM_USECLIPBRD,                 global.fUseClipbrd);
         INIT_MENU_ITEM(IDM_MOUSECHANGESRECURSION, global.fMouseChangesRecursi
         INIT_MENU_ITEM(IDM_ANIMATEPTR,                 global.fAnimatePtr);

         CHECK_MENU_ITEM(global.hwndFrame, IDM_PASTES+global.usRopIndex);


         /* Add ourselves to the switch list. */

         WinQueryWindowProcess(global.hwndFrame, &pid, &tid);
         swctl.hwnd        = global.hwndFrame;
         swctl.idProcess = pid;
         hsw = WinAddSwitchEntry(&swctl);

         return TRUE;
     }
     return FALSE;
 }


 LFPS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFPS.C

 /************************************************************************
 *
 *   lfps.c -- Subroutines for PS/DC/BM management for LineFractal.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFPS
 #define INCL_LFDRAW
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 extern GLOBALDATA global;




 /************************************************************************
 *
 *   Data structures for opening the PostScript DC.
 *
 ************************************************************************/

 typedef struct _randomdata
 {
     long data[12];
 } PSRANDOMDATA;

 typedef struct _psdevopendata
 {
     char *device;
     char *name;
     PSRANDOMDATA *data;
     char *type;
 } PSDEVOPENDATA;

 PSRANDOMDATA psrnddata =
 {
     48, 0, 0x65656565, 0, 0, 0, 0, 0, 0, 0, 0, 1
 };

 PSDEVOPENDATA dvdata =
 {
     "ps.txt",
     "PSCRIPT",
     &psrnddata,
     "IBM_Q_STD"
 };




 /************************************************************************
 *
 *   LfOpenDC
 *
 *   Open a device context for the thread.  Read the DC type from the
 *   thread structure.
 *
 ************************************************************************/

 BOOL fhdcInUse = FALSE; /* TRUE --> a direct dc thread is active */
 HDC hdcPrivate = NULL;        /* non NULL --> it's the window DC */

 BOOL
 LfOpenDC(pthr)
 PTHR  pthr;
 {
     switch (pthr->dcType)
     {
     case IDM_DCDIRECT:
         /* only one direct dc allowed */
         if (fhdcInUse)
             pthr->hdc = NULL;
         else
         {
             if (!hdcPrivate)
                 hdcPrivate = WinOpenWindowDC(global.hwnd);
             pthr->hdc = hdcPrivate;
             fhdcInUse = TRUE;
         }
         break;

     case IDM_DCMEMORY:
         pthr->hdc = DevOpenDC(global.hab, OD_MEMORY, "*", 0L, NULL, NULL);
         break;

     case IDM_DCPOSTSCRIPT:
         pthr->hdc = DevOpenDC(global.hab, OD_DIRECT, "*", 4L, (PDEVOPENDATA)&
         break;

     case IDM_DCPROPRINTER:

     default:
         pthr->hdc = NULL;
     }

     return (pthr->hdc ? TRUE : FALSE);
 }




 /************************************************************************
 *
 *   LfCloseDC
 *
 *   Close the device context for the thread.
 *
 ************************************************************************/

 VOID
 LfCloseDC(pthr)
 PTHR  pthr;
 {
     if (pthr->dcType == IDM_DCDIRECT)
         fhdcInUse = FALSE;
     else
     {
         if (pthr->hbm)
             LfDeleteBitmap(pthr);
         if (pthr->hdc)
             DevCloseDC(pthr->hdc);
     }
     pthr->hdc = NULL;
 }




 /************************************************************************
 *
 *   LfCreateBitmap
 *
 *   Create a bitmap of the format specified in the thread structure.
 *   If the bitmap is to be automatically sized, then read the window
 *   size, otherwise use the values in the thread structure.
 *
 ************************************************************************/

 BOOL
 LfCreateBitmap(pthr)
 PTHR  pthr;
 {
     BITMAPINFOHEADER bminfo;

     bminfo.cbFix = sizeof bminfo;

     bminfo.cx = (USHORT) (pthr->rcl.xRight - pthr->rcl.xLeft);
     bminfo.cy = (USHORT) (pthr->rcl.yTop   - pthr->rcl.yBottom);
     bminfo.cPlanes   = pthr->cPlanes;
     bminfo.cBitCount = pthr->cBitCount;

     pthr->hbm = GpiCreateBitmap(pthr->hps, &bminfo, 0L, 0L, 0L);

     return (pthr->hbm ? TRUE : FALSE);
 }




 /************************************************************************
 *
 *   LfDeleteBitmap
 *
 *   Delete the thread's bitmap and set the bitmap handle to null.
 *
 ************************************************************************/

 VOID
 LfDeleteBitmap(pthr)
 PTHR  pthr;
 {
     GpiDeleteBitmap(pthr->hbm);
     pthr->hbm = NULL;
 }




 /************************************************************************
 *
 *   LfCreatePS
 *
 *   Create a presentation space for the thread.  If the DC type is
 *   a memory bitmap, then create a bitmap, select it into the PS, and
 *   initialize it to the desired background color.  Compute the scale
 *   factors and offsets to use when drawing.
 *
 ************************************************************************/

 BOOL
 LfCreatePS(pthr)
 PTHR  pthr;
 {
     SIZEL size;
     BOOL fOK = FALSE;

     if (LfOpenDC(pthr))
     {
         size.cx = 0L;
         size.cy = 0L;
         pthr->hps = GpiCreatePS(global.hab, pthr->hdc, &size,
                                  PU_PELS|GPIT_MICRO|GPIA_ASSOC);
         if (pthr->hps)
         {
             if (pthr->dcType == IDM_DCDIRECT)
             {
                 pthr->rcl = global.rcl;
                 GpiSetAttrs(pthr->hps, PRIM_LINE,   LFA_LINEALL,  0L, &pthr->
                 GpiSetAttrs(pthr->hps, PRIM_MARKER, LFA_MARKALL,  0L, &pthr->
                 GpiSetAttrs(pthr->hps, PRIM_AREA,   LFA_AREAALL,  0L, &pthr->
                 GpiSetAttrs(pthr->hps, PRIM_IMAGE,  LFA_IMAGEALL, 0L, &pthr->

             }
             else if (pthr->dcType == IDM_DCMEMORY)
             {
                 pthr->rcl.xLeft   = 0L;
                 pthr->rcl.yBottom = 0L;
                 pthr->rcl.xRight  = global.bm.cx;
                 pthr->rcl.yTop          = global.bm.cy;
                 pthr->cPlanes          = global.bm.cPlanes;
                 pthr->cBitCount   = global.bm.cBitCount;
                 if (LfCreateBitmap(pthr))
                     if (GpiSetBitmap(pthr->hps, pthr->hbm) != HBM_ERROR)
                     {
                         GpiSetAttrs(pthr->hps, PRIM_LINE,   LFA_LINEALL,  0L,
                         GpiSetAttrs(pthr->hps, PRIM_MARKER, LFA_MARKALL,  0L,
                         GpiSetAttrs(pthr->hps, PRIM_AREA,   LFA_AREAALL,  0L,
                         GpiSetAttrs(pthr->hps, PRIM_IMAGE,  LFA_IMAGEALL, 0L,

                         LfClearRect(pthr, &pthr->rcl);
                         if (pthr->fCollectBounds)
                             GpiSetDrawControl(pthr->hps, DCTL_BOUNDARY, DCTL_
                         fOK = TRUE;
                     }

                 if (!fOK)
                 {
                     LfDeletePS(pthr);
                     return FALSE;
                 }
             }
             else
             {
                 pthr->rcl = global.rcl;
             }

             pthr->flMiscAttrs |= LFA_XSCALE | LFA_YSCALE | LFA_XOFF | LFA_YOF

             return TRUE;
         }
     }
     return FALSE;
 }




 /************************************************************************
 *
 *   LfDeletePS
 *
 *   Delete the thread's presentation space.  If the DC type is
 *   a memory bitmap, then unset the bitmap and delete it.
 *
 ************************************************************************/

 VOID
 LfDeletePS(pthr)
 PTHR  pthr;
 {
     if (pthr->hbm)
     {
         GpiSetBitmap(pthr->hps, NULL);
         LfDeleteBitmap(pthr);
     }
     GpiDestroyPS(pthr->hps);
     LfCloseDC(pthr);
 }




 /************************************************************************
 *
 *   LfResizePS
 *
 *   Destroy the current PS and create a new one the size of the
 *   current client rectangle.
 *
 ************************************************************************/

 BOOL
 LfResizePS(pthr)
 PTHR pthr;
 {
     if (pthr->hps)
     {
         pthr->fInterrupted = TRUE;
         while (pthr->fBusy)
             ;
         LfDeletePS(pthr);
     }

     return LfCreatePS(pthr);
 }


 LFTHREAD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFTHREAD.C

 /************************************************************************
 *
 *   lfthread.c -- Subroutines for thread management for LineFractal.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSMEMMGR
 #define INCL_DOSPROCESS
 #define INCL_DOSERRORS
 #include <os2.h>
 #define  _MT
 #include <process.h>
 #include <stddef.h>

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFCMD
 #define INCL_LFMAIN
 #define INCL_LFPS
 #define INCL_LFUTIL
 #define INCL_LFTHREAD
 #define INCL_LFDRAW
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 extern GLOBALDATA global;




 /************************************************************************
 *
 *   LfStartThread
 *
 *   Do everything needed to start up a background thread drawing
 *   fractals.
 *
 *   This includes:
 *
 *        Allocate space for thread structure.
 *        Initialize fields of thread structure.
 *        Create a presentation space for the thread.
 *        Create stack space for the thread.
 *        Set the thread's semaphore to block -- i.e. don't start
 *            drawing until everything is ready.
 *        Start the thread.
 *        Set the thread's priority to idle time.
 *
 ************************************************************************/

 #define SIZE_ACCUM_STACK        2048

 PTHR
 LfStartThread(dcType)
 USHORT dcType;
 {
     PTHR pthr;


     pthr = NULL;        /* zero out all 32 bits */
     if (DosAllocSeg(sizeof(THR), ((PUSHORT)&pthr)+1, 0))
         return NULL;

     pthr->dcType = dcType;

     LfPreInitThread(pthr);
     if (LfCreatePS(pthr))
     {
         LfPostInitThread(pthr);
         if (DosAllocSeg(SIZE_ACCUM_STACK, &pthr->selStack, 0))
             goto lfst_exit;

         DosSemSet(&pthr->lSemRedraw);
         pthr->tid = _beginthread(LineFractalThread, MAKEP(pthr->selStack, 0),
         if (pthr->tid == -1)
         {
             LfDeletePS(pthr);
             goto lfst_exit;
         }
         DosSetPrty(PRTYS_THREAD, PRTYC_IDLETIME, 0, pthr->tid);

         LfSelectXform(global.hwnd, pthr, pthr->usCurXform);
         DosSemClear(&pthr->lSemRedraw);

         if (!DevQueryCaps(pthr->hdc, CAPS_HORIZONTAL_RESOLUTION, 2L, (PLONG)&
         {
             pthr->AspectRatio.lHorz = 1L;
             pthr->AspectRatio.lVert = 1L;
         }

         return pthr;
     }
 lfst_exit:
     DosFreeSeg(*(((PUSHORT)&pthr)+1));
     return NULL;
 }




 /************************************************************************
 *
 *   LfPreInitThread
 *
 *   Initialize constant values in the thread structure needed from
 *   the moment the thread starts.  If thread inheritance is enabled,
 *   then copy as much as possible from the top thread, if there is
 *   one.
 *
 ************************************************************************/

 VOID
 LfPreInitThread(pthr)
 PTHR pthr;
 {
     pthr->hps                = NULL;
     pthr->hdc                = NULL;
     pthr->hbm                = NULL;
     pthr->fTimeToDie        = FALSE;
     pthr->fUpdateAttrs        = FALSE;
     pthr->fBusy         = FALSE;
     pthr->pptl                = NULL;
     pthr->cptl                = 0;
     pthr->pmatlf        = NULL;

     if (global.fThreadInheritance && global.pThrTop)
     {
         pthr->cPlanes               = global.pThrTop->cPlanes          ;
         pthr->cBitCount        = global.pThrTop->cBitCount          ;

         pthr->cxWCS               = global.pThrTop->cxWCS                  ;
         pthr->cyWCS               = global.pThrTop->cyWCS                  ;

         pthr->fAutoSizePS      = global.pThrTop->fAutoSizePS          ;
         pthr->fAutoSelectDims  = global.pThrTop->fAutoSelectDims  ;
         pthr->fClearOnRedraw   = global.pThrTop->fClearOnRedraw   ;
         pthr->fAutoStartRedraw = global.pThrTop->fAutoStartRedraw ;
         pthr->fCollectBounds   = global.pThrTop->fCollectBounds   ;

         pthr->fFracRedraw      = global.pThrTop->fFracRedraw          ;
         pthr->fPrimRedraw      = global.pThrTop->fPrimRedraw          ;
         pthr->fAttrRedraw      = global.pThrTop->fAttrRedraw          ;

         pthr->lb               = global.pThrTop->lb                  ;
         pthr->flLineAttrs      = LFA_LINEALL                          ;
         pthr->mb               = global.pThrTop->mb                  ;
         pthr->flMarkerAttrs    = LFA_MARKALL                          ;
         pthr->ab               = global.pThrTop->ab                  ;
         pthr->flAreaAttrs      = LFA_AREAALL                          ;
         pthr->ib               = global.pThrTop->ib                  ;
         pthr->flImageAttrs     = LFA_IMAGEALL                          ;

         pthr->usCurPrim        = global.pThrTop->usCurPrim          ;
         pthr->usCurXform       = global.pThrTop->usCurXform          ;
         pthr->usRecursion      = global.pThrTop->usRecursion          ;
         pthr->usPolygonSides   = global.pThrTop->usPolygonSides   ;
         pthr->cptMax               = global.pThrTop->cptMax           ;
         pthr->dblXOff               = global.pThrTop->dblXOff          ;
         pthr->dblYOff               = global.pThrTop->dblYOff          ;
         pthr->dblXScale        = global.pThrTop->dblXScale          ;
         pthr->dblYScale        = global.pThrTop->dblYScale          ;
         pthr->dblRotation      = global.pThrTop->dblRotation          ;
         pthr->flMiscAttrs      = LFA_MISCALL                          ;
     }
     else
     {
         pthr->cPlanes               = global.bm.cPlanes;
         pthr->cBitCount        = global.bm.cBitCount;

         pthr->cxWCS               = 10000L;
         pthr->cyWCS               = 10000L;

         pthr->fAutoSizePS      = TRUE;
         pthr->fAutoSelectDims  = TRUE;
         pthr->fClearOnRedraw   = TRUE;
         pthr->fAutoStartRedraw = FALSE;
         pthr->fCollectBounds   = TRUE;

         pthr->fFracRedraw      = TRUE;
         pthr->fPrimRedraw      = TRUE;
         pthr->fAttrRedraw      = TRUE;

         pthr->lb.lColor        = CLR_NEUTRAL;
         pthr->lb.usMixMode     = FM_OVERPAINT;
         pthr->lb.fxWidth       = LINEWIDTH_NORMAL;
 ;         pthr->lb.lGeomWidth        = 1L;
         pthr->lb.usType        = LINETYPE_SOLID;
 ;         pthr->lb.usEnd         = LINEEND_FLAT;
 ;         pthr->lb.usJoin        = LINEJOIN_BEVEL;
         pthr->flLineAttrs      = LFA_LINEALL;

         pthr->mb.lColor        = CLR_NEUTRAL;
         pthr->mb.lBackColor    = CLR_BACKGROUND;
         pthr->mb.usMixMode     = FM_OVERPAINT;
         pthr->mb.usBackMixMode = BM_LEAVEALONE;
         pthr->mb.usSet               = LCID_DEFAULT;
         pthr->mb.usSymbol      = MARKSYM_DIAMOND;
 ;         pthr->mb.sizfxCell.cx        = 0L;
 ;         pthr->mb.sizfxCell.cy        = 0L;
         pthr->flMarkerAttrs    = LFA_MARKALL;

         pthr->ab.lColor        = CLR_NEUTRAL;
         pthr->ab.lBackColor    = CLR_BACKGROUND;
         pthr->ab.usMixMode     = FM_OVERPAINT;
         pthr->ab.usBackMixMode = BM_OVERPAINT;
         pthr->ab.usSet               = LCID_DEFAULT;
         pthr->ab.usSymbol      = PATSYM_NOSHADE;
 ;         pthr->ab.ptlRefPoint.x = 0L;
 ;         pthr->ab.ptlRefPoint.y = 0L;
         pthr->flAreaAttrs      = LFA_AREAALL;

         pthr->ib.lColor        = CLR_NEUTRAL;
         pthr->ib.lBackColor    = CLR_BACKGROUND;
         pthr->ib.usMixMode     = FM_OVERPAINT;
         pthr->ib.usBackMixMode = FM_OVERPAINT;
         pthr->flImageAttrs     = LFA_IMAGEALL;

         pthr->usCurPrim        = IDM_POLYLINE;
         pthr->usCurXform       = IDM_SAWTOOTH;
         pthr->usRecursion      = 1;
         pthr->usPolygonSides   = 3;
         pthr->cptMax               = MAX_POINT_COUNT;
         pthr->dblXOff               = 0.125;
         pthr->dblYOff               = 0.5;
         pthr->dblXScale        = 0.75;
         pthr->dblYScale        = 0.75;
         pthr->dblRotation      = 0.0;
         pthr->flMiscAttrs      = LFA_MISCALL;
     }

 }




 /************************************************************************
 *
 *   LfPostInitThread
 *
 *   Initialize various values in the thread structure which required
 *   that a PS/DC/BM had been created.  If thread inheritance is enabled,
 *   then copy as much as possible from the top thread, if there is
 *   one.
 *
 ************************************************************************/

 VOID
 LfPostInitThread(pthr)
 PTHR pthr;
 {
     if (!(global.fThreadInheritance && global.pThrTop))
     {
         pthr->lb.lGeomWidth = GpiQueryLineWidthGeom(pthr->hps);
         pthr->lb.usEnd            = (USHORT) GpiQueryLineEnd(pthr->hps);
         pthr->lb.usJoin     = (USHORT) GpiQueryLineJoin(pthr->hps);

         DevQueryCaps(pthr->hdc, CAPS_MARKER_WIDTH,  1L, (PLONG)&(pthr->mb.siz
         DevQueryCaps(pthr->hdc, CAPS_MARKER_HEIGHT, 1L, (PLONG)&(pthr->mb.siz

         GpiQueryPatternRefPoint(pthr->hps,&(pthr->ab.ptlRefPoint));
     }
 }




 /************************************************************************
 *
 *   LfKillThread
 *
 *   Force the given thread to die and release its resources.
 *
 ************************************************************************/

 VOID
 LfKillThread(pthr)
 PTHR pthr;
 {
     USHORT i;
          USHORT j;


     /* This does not do anything if there aren't any threads. */

     if (global.cThr && pthr)
     {
         /****************************************************************
         *  Tell the thread to die when it can.
         *  If blocked on lSemRedraw, then it will recognize fTimeToDie
         *  as soon as it's unblocked.  If it's currently drawing,
         *  it will recognize fInterrupted, kick out of the recursion,
         *  loop back up to check lSemRedraw, which will be clear, so
         *  it'll do as already mentioned.  Set the priority to time-
         *  critical to speed things up.
         ****************************************************************/

         pthr->fTimeToDie   = TRUE;
         pthr->fInterrupted = TRUE;
         DosSetPrty(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, pthr->tid);
         DosSemClear(&pthr->lSemRedraw);


         /****************************************************************
         *  Once it's decided it must die, it will clean up after
         *  itself and exit quietly.  To detect when it has actually
         *  exited, so that we can safely free up the stack, we will
         *  loop asking for the thread's priority until an error occurs.
         *  We will assume that an error implies the thread has exited.
         ****************************************************************/

         {
             USHORT usT;
             USHORT usRet;
             while (!(usRet = DosGetPrty(PRTYS_THREAD, &usT, pthr->tid)));
             if (usRet != ERROR_INVALID_THREADID)
             {
                 MyMessageBox(global.hwnd,
                     "Cannot kill thread");
                 return;
             }
         }


         /****************************************************************
         *  Extract the given thread from the group. This is done
         *  inside a critical section because the drawing threads
         *  call LfIsThreadTop, which reads global.aThr.
         ****************************************************************/

         i = 0;
         while ((i < global.cThr) && (pthr != global.aThr[i]))
             ++i;    /* if i == global.cThr we have a problem */
         DosEnterCritSec();
         for (j = i; j < global.cThr-1; ++j)
             global.aThr[j] = global.aThr[j+1];
         global.aThr[j] = NULL;        /* clear out all copies of pthr */
         --global.cThr;
         DosExitCritSec();


         /****************************************************************
         *  Free up resources allocated for the thread.
         ****************************************************************/

         LfDeletePS(pthr);
         DosFreeSeg(pthr->selStack);
         DosFreeSeg(*(((PUSHORT)&pthr)+1));
     }
 }




 /************************************************************************
 *
 *   LfBringThreadToTop
 *
 *   Bring the specified thread to the top, and invalidate the window
 *   to force a WM_PAINT message to make the thread's image visible.
 *   The given thread can be NULL, in which case just set pThrTop to
 *   NULL.
 *
 ************************************************************************/

 VOID
 LfBringThreadToTop(pthr)
 PTHR pthr;
 {
     USHORT i;


     /********************************************************************
     *  Find the given thread in the group.
     ********************************************************************/

     if (pthr)
     {
         i = 0;
         while ((i < global.cThr) && (pthr != global.aThr[i]))
             ++i;    /* if i == global.cThr, we have problems */
     }
     else
         i = 32767; /* should cause gp fault if used */


     /********************************************************************
     *  If the user wants the menu items updated, and we're switching
     *  away from an active thread, then uncheck the current thread's
     *  settings.
     ********************************************************************/

     if (global.fUpdateMenusOnThreadSwitch)
     {
         if (global.pThrTop)
         {
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_AUTORESIZE);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_AUTOSELECTDIMS);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_CLEARONREDRAW);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_AUTOSTARTREDRAW);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_COLLECTBOUNDS);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_FRACREDRAW);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_PRIMREDRAW);
             UNCHECK_MENU_ITEM(global.hwndFrame, IDM_ATTRREDRAW);
             UNCHECK_MENU_ITEM(global.hwndFrame, global.pThrTop->usCurPrim);
             UNCHECK_MENU_ITEM(global.hwndFrame, global.pThrTop->usCurXform);
             UNCHECK_MENU_ITEM(global.hwndFrame, global.pThrTop->dcType);
         }
     }


     /********************************************************************
     *  Switch the given thread to the top.
     ********************************************************************/

     DosEnterCritSec();
     global.iThrTop = i;
     global.pThrTop = pthr;
     DosExitCritSec();


     /********************************************************************
     *  If we switched in a living thread, then invalidate the entire
     *  window.        This will cause a WM_PAINT message to be sent, which
     *  will display the image from the new top thread.        If the user
     *  has not disabled updating of the menus, then do that.
     ********************************************************************/

     if (pthr)
     {
         WinInvalidateRect(global.hwnd, NULL, FALSE);

         /* If the user wants menu items updated, then do it now. */
         if (global.fUpdateMenusOnThreadSwitch)
         {
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_AUTORESIZE,           pthr
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_AUTOSELECTDIMS,  pthr->fAu
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_CLEARONREDRAW,   pthr->fCl
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_AUTOSTARTREDRAW, pthr->fAu
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_COLLECTBOUNDS,   pthr->fCo
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_FRACREDRAW,           pthr
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_PRIMREDRAW,           pthr
             TOGGLE_MENU_ITEM(global.hwndFrame, IDM_ATTRREDRAW,           pthr
             CHECK_MENU_ITEM (global.hwndFrame, pthr->usCurPrim);
             CHECK_MENU_ITEM (global.hwndFrame, pthr->usCurXform);
             CHECK_MENU_ITEM (global.hwndFrame, pthr->dcType);

             LfUpdateThreadMenu();
         }
     }
 }




 /************************************************************************
 *
 *   LfIsThreadTop
 *
 *   Returns TRUE if the given thread is the top one.  Putting this
 *   test in a function isolates it and makes it easier to maintain
 *   a critical section.
 *
 ************************************************************************/

 BOOL
 LfIsThreadTop(pthr)
 PTHR pthr;
 {
     BOOL f;

     f = FALSE;
     DosEnterCritSec();
     if (pthr == global.pThrTop)
         f = TRUE;
     DosExitCritSec();

     return f;
 }




 /************************************************************************
 *
 *   LfUpdateThreadMenu
 *
 *   Brings the "Thread Bring thread to top" submenu in line with the
 *   currently active threads.
 *
 ************************************************************************/

 VOID
 LfUpdateThreadMenu()
 {
     /*
      * Enable the numbers for threads that do exist.
      * Disable the numbers for threads that don't exist.
      * Uncheck everything, then check the right one.
      */

     USHORT i;

     for (i = 0; i < global.cThr; ++i)
     {
         UNCHECK_MENU_ITEM(global.hwndFrame, IDM_THR0TOTOP+i);
         ENABLE_MENU_ITEM (global.hwndFrame, IDM_THR0TOTOP+i);
     }

     for (     ; i < MAX_THREADS; ++i)
     {
         UNCHECK_MENU_ITEM(global.hwndFrame, IDM_THR0TOTOP+i);
         DISABLE_MENU_ITEM(global.hwndFrame, IDM_THR0TOTOP+i);
     }

     CHECK_MENU_ITEM(global.hwndFrame, IDM_THR0TOTOP + global.iThrTop);
 }


 LFUTIL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFUTIL.C

 /************************************************************************
 *
 *   lfutil.c -- Utility subroutines.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include "linefrac.h"

 #define INCL_LFUTIL
 #include "lffuncs.h"




 /************************************************************************
 *
 *   MySetWindowUShort
 *
 *   Sets the given control id to the value specified.
 *
 ************************************************************************/

 VOID
 MySetWindowUShort(hWnd, id, num)
 HWND hWnd;
 USHORT id;
 USHORT num;
 {
     char szStr[CCHSTR];

     sprintf((NPCH)szStr, "%u", num);
     WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
 }




 /************************************************************************
 *
 *   MySetWindowLong
 *
 *   Sets the given control id to the value specified.
 *
 ************************************************************************/

 VOID
 MySetWindowLong(hWnd, id, num)
 HWND hWnd;
 USHORT id;
 LONG num;
 {
     char szStr[CCHSTR];

     sprintf((NPCH)szStr, "%ld", num);
     WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
 }




 /************************************************************************
 *
 *   MySetWindowDouble
 *
 *   Sets the given control id to the value specified.
 *
 ************************************************************************/

 VOID
 MySetWindowDouble(hWnd, id, num)
 HWND hWnd;
 USHORT id;
 double num;
 {
     char szStr[CCHSTR];

     sprintf((NPCH)szStr, "%lf", num);
     WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
 }




 /************************************************************************
 *
 *   MyGetWindowUShort
 *
 *   Returns the value from the given control id.
 *
 ************************************************************************/

 VOID
 MyGetWindowUShort(hWnd, id, pus)
 HWND hWnd;
 USHORT id;
 PUSHORT pus;
 {
     char szStr[CCHSTR];

     WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);
     *pus = atoi(szStr);
 }




 /************************************************************************
 *
 *   MyGetWindowLong
 *
 *   Returns the value from the given control id.
 *
 ************************************************************************/

 VOID
 MyGetWindowLong(hWnd, id, pl)
 HWND hWnd;
 USHORT id;
 PLONG  pl;
 {
     char szStr[CCHSTR];

     WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);
     *pl = atol(szStr);
 }




 /************************************************************************
 *
 *   MyGetWindowDouble
 *
 *   Returns the value from the given control id.
 *
 ************************************************************************/

 VOID
 MyGetWindowDouble(hWnd, id, pdbl)
 HWND   hWnd;
 USHORT id;
 PDBL   pdbl;
 {
     char szStr[CCHSTR];

     WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);
     *pdbl = atof(szStr);
 }




 /************************************************************************
 *
 *   MyMessageBox
 *
 *   Displays a message box with the given string.  To simplify matters,
 *   the box will always have the same title ("LineFractal"), will always
 *   have a single button ("Ok"), will always have an exclamation point
 *   icon, and will always be application modal.
 *
 ************************************************************************/

 VOID
 MyMessageBox(hWnd, sz)
 HWND hWnd;
 PSZ sz;
 {
     static char *szTitle = "LineFractal";

     WinMessageBox(HWND_DESKTOP, hWnd, sz, szTitle, 0,
                   MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
 }


 LFXFORM.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LFXFORM.C

 /************************************************************************
 *
 *   lfxform.c -- Built-in similarity transforms.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_GPI
 #include <os2.h>
 #include "linefrac.h"




 #define SQRT_2        1.4142136
 #define SQRT_3        1.7320508




 /************************************************************************
 *
 *   SharkTooth
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *
 *                                 *
 *                     ===>      *   *
 *                             *             *
 *   ***************           *               *
 *
 ************************************************************************/

 LINEFRAC SharkTooth[] =
 {
     &SharkTooth[1],  PI / 6.0, (1.0/SQRT_3), FALSE,
     EOLIST,            -PI / 3.0, (1.0/SQRT_3), FALSE
 };




 /************************************************************************
 *
 *   SawTooth
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *
 *
 *                     ===>      *
 *                             *         *
 *   ***************           *           *           *
 *                                     *         *
 *                                       *
 *
 ************************************************************************/

 LINEFRAC SawTooth[] =
 {
     &SawTooth[1],  PI / 4.0, (0.5 / SQRT_2), FALSE,
     &SawTooth[2], -PI / 2.0, (1.0 / SQRT_2), FALSE,
     EOLIST,           PI / 2.0, (0.5 / SQRT_2), FALSE
 };




 /************************************************************************
 *
 *   KochIsland
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *   This is known as the Koch, or snowflake, transform.
 *
 *
 *                                  *
 *                     ===>        * *
 *                                *   *
 *   ***************           *****     *****
 *
 ************************************************************************/

 LINEFRAC Koch[] =
 {
      &Koch[1],                 0.0, 1.0 / 3.0, FALSE,
      &Koch[2],            PI / 3.0, 1.0 / 3.0, FALSE,
      &Koch[3], -2 * PI / 3.0, 1.0 / 3.0, FALSE,
      EOLIST,            PI / 3.0, 1.0 / 3.0, FALSE
 };




 /************************************************************************
 *
 *   SpaceFiller
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *
 *                           ***B******C***
 *                           *                *
 *                           A                D
 *   **************   ===>  *                *
 *
 *
 *   where sides A and D are reflections.
 *
 *
 *
 ************************************************************************/

 LINEFRAC SpaceFiller[] =
 {
     &SpaceFiller[1],  PI / 2.0, 0.5, TRUE,
     &SpaceFiller[2], -PI / 2.0, 0.5, FALSE,
     &SpaceFiller[3],           0.0, 0.5, FALSE,
     EOLIST,             -PI / 2.0, 0.5, TRUE
 };




 /************************************************************************
 *
 *   StovePipe
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *   This is sometimes known as the stovepipe transform.
 *
 *
 *                               ******
 *                               *    *
 *                     ===>      *    *
 *                               *    *
 *   **************           *****    *****
 *
 ************************************************************************/

 LINEFRAC StovePipe[] =
 {
     &StovePipe[1],         0.0, 1.0 / 3.0, FALSE,
     &StovePipe[2],  PI / 2.0, 1.0 / 3.0, FALSE,
     &StovePipe[3], -PI / 2.0, 1.0 / 3.0, FALSE,
     &StovePipe[4], -PI / 2.0, 1.0 / 3.0, FALSE,
     EOLIST,            PI / 2.0, 1.0 / 3.0, FALSE
 };




 /************************************************************************
 *
 *   SquareWave
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *
 *                           ******
 *                           *        *
 *   **************   ===>  *        *    *
 *                                *    *
 *                                ******
 *
 ************************************************************************/

 LINEFRAC SquareWave[] =
 {
     &SquareWave[1],  PI / 2.0, 0.25, FALSE,
     &SquareWave[2], -PI / 2.0, 0.5,  FALSE,
     &SquareWave[3], -PI / 2.0, 0.5,  FALSE,
     &SquareWave[4],  PI / 2.0, 0.5,  FALSE,
     EOLIST,             PI / 2.0, 0.25, FALSE
 };




 /************************************************************************
 *
 *   HourGlass
 *
 *   Set up the similarity transform for the following linefractal,
 *   which looks roughly like:
 *
 *
 *                              * * * * *
 *                                *   *
 *                                  *
 *                     ===>        *   *
 *                              *       *
 *   **************            *                *
 *
 ************************************************************************/

 #define HOURGLASSANGLE        0.463648
 #define HOURGLASSLEN        0.83852549

 LINEFRAC HourGlass[] =
 {
     &HourGlass[1],     HOURGLASSANGLE, HOURGLASSLEN, FALSE,
     &HourGlass[2],  PI-HOURGLASSANGLE, 0.5,             FALSE,
     EOLIST,           -PI-HOURGLASSANGLE, HOURGLASSLEN, FALSE
 };




 /************************************************************************
 *
 *   aXform
 *
 *   This lists all the built-in transforms available, with their default
 *   coordinate offsets and scale factors.
 *
 ************************************************************************/

 XFORMDATA aXform[] =
 {
     SharkTooth,  0.200,  0.200,  0.60, 0.60, 3,
     SawTooth,         0.125,  0.125,  0.75, 0.75, 3,
     Koch,         0.125,  0.125,  0.75, 0.75, 3,
     SpaceFiller, 0.000, -0.500,  1.00, 1.00, 1,
     StovePipe,         0.125,  0.125,  0.75, 0.75, 4,
     SquareWave,  0.200,  0.200,  0.60, 0.60, 5,
     HourGlass,         0.250,  0.250,  0.50, 0.50, 2
 };


 LINEFRAC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\LINEFRAC\LINEFRAC.C

 /************************************************************************
 *
 *   linefrac.c -- Main window procedure for LineFractal window class.
 *
 *   Created by Microsoft Corporation, 1989
 *
 ************************************************************************/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSMEMMGR
 #define INCL_DOSPROCESS
 #include <os2.h>

 #define _MT
 #include <process.h>

 #include <stdlib.h>

 #define INCL_GLOBALS
 #define INCL_THREADS
 #include "linefrac.h"

 #define INCL_LFMAIN
 #define INCL_LFINIT
 #define INCL_LFTHREAD
 #define INCL_LFPS
 #define INCL_LFCMD
 #define INCL_LFDRAW
 #include "lffuncs.h"




 /************************************************************************
 *
 *   Global Variables
 *
 ************************************************************************/

 GLOBALDATA global;




 /************************************************************************
 *
 *   main
 *
 *   WinInitialize resizes our ring 2 stack, among other things, so
 *   we won't GP fault trying to do graphics.  WinCreateMsgQueue defines
 *   us as a REAL PM app. (as does the WINDOWAPI in the .DEF file).
 *   Call a subroutine to register our window class and create a window.
 *   Loop over messages.  Exit cleanly.
 *
 ************************************************************************/

 VOID cdecl
 main( VOID )
 {
     QMSG qMsg;
     int iRet = 0;

     global.hab         = WinInitialize(0);
     global.hMsgQ = WinCreateMsgQueue(global.hab, 0);

     if (LfInitApp())
         while (WinGetMsg( global.hab, (PQMSG)&qMsg, (HWND)NULL, 0, 0 ))
             WinDispatchMsg( global.hab, (PQMSG)&qMsg );
     else
         iRet = -1;

     WinDestroyWindow( global.hwndFrame );
     WinDestroyMsgQueue( global.hMsgQ );
     WinTerminate( global.hab );
     DosExit(EXIT_PROCESS, iRet);
 }




 /************************************************************************
 *
 *   LineFracWndProc
 *
 *   Process messages for the LineFractal window class.
 *
 ************************************************************************/

 ULONG CALLBACK
 LineFracWndProc( hwnd, usMsg, mp1, mp2 )
 HWND   hwnd;
 USHORT usMsg;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     HPS     hps;
     USHORT  iNewTop;
     USHORT  i;
     PTHR pthr;
     RECTL rcl;
     BOOL  fIsTimerUsed;

     switch (usMsg)
     {
     case WM_CREATE:
         if ((global.hptr)[global.usCurPtr])
             WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
         break;

     case WM_CLOSE:
         LfClose(hwnd);
         break;

     case WM_COMMAND:
         LfCommand(hwnd, LOUSHORT(mp1));
         break;

     case WM_TIMER:
         if (LOUSHORT(mp1) == IDT_AUTOSTARTREDRAW)
         {
             fIsTimerUsed = FALSE;
             for (i = 0; i < global.cThr; ++i)
                 if (global.aThr[i]->fAutoStartRedraw)
                 {
                     DosSemClear(&(global.aThr[i])->lSemRedraw);
                     fIsTimerUsed = TRUE;
                 }
             if (!fIsTimerUsed)
                 LfStopRedrawTimer();
             else
             {
                 LfStopRedrawTimer();
                 LfStartRedrawTimer();
             }
         }
         else if (LOUSHORT(mp1) == IDT_AUTOSWITCH)
         {
             DosEnterCritSec();
             iNewTop = (global.iThrTop + 1) % global.cThr;
             DosExitCritSec();
             LfBringThreadToTop(global.aThr[iNewTop]);
         }
         else
             goto pass_it_on;
         break;

     case WM_ERASEBACKGROUND:
         LfEraseBackground(hwnd, NULL, NULL, NULL);
         return FALSE;
         break;

     case WM_PAINT:
         if (!global.pThrTop)
             WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_DCMEMORY, 0);

         hps = WinBeginPaint(global.hwnd, NULL, &rcl);
         if (global.pThrTop)   /* only try to paint if we have a drawing */
             LfPaint(hps, &rcl);
         WinEndPaint(hps);
         break;

     case WM_CHAR:
         if (LOUSHORT(mp1) & KC_CHAR)
    {
      LfChar(hwnd, (CHAR)(USHORT)(ULONG)(mp2));
         }
         break;

     case WM_BUTTON1DOWN:
     case WM_BUTTON2DOWN:
         LfButtonDown(hwnd, mp1);
         WinSetFocus(HWND_DESKTOP, hwnd);
         break;

     case WM_BUTTON1UP:
     case WM_BUTTON2UP:
         LfButtonUp(usMsg);
         break;

     case WM_MOUSEMOVE:
         LfMouseMove();
         break;

     case WM_SIZE:
         /* Resize each PS that has fAutoSizePS set to TRUE. */

         WinQueryWindowRect(global.hwnd, &global.rcl);
         global.bm.cx = (USHORT) (global.rcl.xRight - global.rcl.xLeft);
         global.bm.cy = (USHORT) (global.rcl.yTop   - global.rcl.yBottom);

         for (i = 0; i < global.cThr; ++i)
             if (pthr = global.aThr[i])
                 if (pthr->hps)
                     if (pthr->fAutoSizePS)
                     {
                         global.bm.cPlanes   = pthr->cPlanes;
                         global.bm.cBitCount = pthr->cBitCount;

                         if (LfResizePS(pthr))
                             DosSemClear(&pthr->lSemRedraw);
                     }

         /* fall through -- we might want to restart point accumulation,
            but don't want to process the resize message */

     default:
 pass_it_on:
         return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
         break;
     }

     return FALSE;
 }




 /************************************************************************
 *
 *   LfClose
 *
 *   Kill all the threads still running and delete all our fancy pointers.
 *   In general, prepare to terminate the program.
 *
 ************************************************************************/

 VOID
 LfClose(hwnd)
 HWND hwnd;
 {
     int i;

     WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_KILLALLTHREADS, 0);
     WinSetPointer(HWND_DESKTOP,
                   WinQuerySysPointer(HWND_DESKTOP,SPTR_ARROW,FALSE));
     for (i = 0; i < NUM_PTR_SHAPES; ++i)
         if ((global.hptr)[i])
             WinDestroyPointer((global.hptr)[i]);
     if (global.hptrTrack)
         WinDestroyPointer(global.hptrTrack);
     if (global.hptrPaste)
         WinDestroyPointer(global.hptrPaste);
     WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
 }




 /************************************************************************
 *
 *   LfEraseBackground
 *
 *   Erase the window background to a hatch pattern.  This enables the
 *   user to see where the bitmap's edges are in case it's smaller
 *   than the window.
 *
 ************************************************************************/

 VOID
 LfEraseBackground(hwnd, hpsCaller, prclUpdate, prclX)
 HWND hwnd;
 HPS hpsCaller;
 PRECTL prclUpdate;
 PRECTL prclX;        /* excluded rectangle */
 {
     HPS hps;
     RECTL rcl;
     RECTL rclT;
     AREABUNDLE ab;
     HRGN hrgnClipOld;
     HRGN hrgn;
     HRGN hrgnT;


     if (hpsCaller)
         hps = hpsCaller;
     else
         hps = WinGetPS(hwnd);

     if (prclUpdate)
         rcl = *prclUpdate;
     else
         WinQueryUpdateRect(hwnd, (PRECTL)&rcl);

     ab.lColor          = CLR_BLACK;
     ab.lBackColor = CLR_WHITE;
     ab.usSymbol   = PATSYM_DIAG1;
     GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR|ABB_BACK_COLOR|ABB_SYMBOL,
                 0L, (PBUNDLE)&ab);


     /* Make a region out of the nearest rectangle, then copy the real
      * region data into it.  Set this as our clip rectangle.
      */
     if ((hrgn = GpiCreateRegion(hps, 1L, &rcl)) != HRGN_ERROR)
     {
         WinQueryUpdateRegion(hwnd, hrgn);
         GpiSetClipRegion(hps, hrgn, &hrgnClipOld);
     }

     if (!prclX)
         GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rcl, ROP_PATCOPY, 0L);
     else
     {
         if (prclX->yTop < rcl.yTop)
         {
             rclT.xLeft         = rcl.xLeft;
             rclT.yBottom = prclX->yBottom;
             rclT.xRight  = rcl.xRight;
             rclT.yTop         = rcl.yTop;
             GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, 0L);
         }

         if (prclX->xRight < rcl.xRight)
         {
             rclT.xLeft         = prclX->xRight;
             rclT.yBottom = rcl.yBottom;
             rclT.xRight  = rcl.xRight;
             rclT.yTop         = prclX->yTop;
             GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, 0L);
         }
     }

     if (hrgnClipOld != HRGN_ERROR)
         GpiSetClipRegion(hps, hrgnClipOld, &hrgnT);
     if (hrgn != HRGN_ERROR)
         GpiDestroyRegion(hps, hrgn);

     if (!hpsCaller)
         WinReleasePS(hps);
 }




 /************************************************************************
 *
 *   LfPaint
 *
 *   If we have a bitmap, blt it to the screen, no matter what state
 *   it's in.  If the selection rectangle is still alive, then display
 *   it, too.  Note that it goes directly to the screen, so we have to
 *   redraw it each time the bitmap is thrown back up.
 *
 ************************************************************************/

 VOID
 LfPaint(hps, prcl)
 HPS  hps;
 PRECTL prcl;
 {
     POINTL  aptl[4];

     if (global.pThrTop)
     {
         switch (global.pThrTop->dcType)
         {
         case IDM_DCDIRECT:        /* all drawing is already on the screen */
             break;

         case IDM_DCPOSTSCRIPT:
         case IDM_DCPROPRINTER:
             break;

         case IDM_DCMEMORY:

             GpiSetAttrs(hps, PRIM_IMAGE, IBB_COLOR|IBB_BACK_COLOR, 0L, &globa

             aptl[0].x = 0L;
             aptl[0].y = 0L;
             aptl[1].x = (global.pThrTop->rcl).xRight;
             aptl[1].y = (global.pThrTop->rcl).yTop;
             aptl[2].x = 0L;
             aptl[2].y = 0L;

             GpiBitBlt(hps, global.pThrTop->hps, 3L, aptl, ROP_SRCCOPY, (LONG)
             LfEraseBackground(global.hwnd, hps, prcl, &(global.pThrTop->rcl))
             break;

         default:
             break;
         }
         if (global.fShowSelectRc)
             LfShowSelectRc(hps, global.fTempSelect ? &global.rclSelect : &glo
     }
 }




 /************************************************************************
 *
 *   LfShowSelectRc
 *
 *   Draw the selection rectangle in the given presentation space.  The
 *   rectangle is drawn in xor-mode so this can be called to remove it
 *   as well as show it.
 *
 ************************************************************************/

 VOID
 LfShowSelectRc(hps, lprc)
 HPS hps;
 PRECTL lprc;
 {
     LINEBUNDLE lb;


     lb.lColor         = CLR_TRUE;
     lb.usMixMode = FM_XOR;
     lb.usType         = LINETYPE_ALTERNATE;
     GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR|LBB_MIX_MODE|LBB_TYPE,
                 0L, (PBUNDLE)&lb);
     GpiSetCurrentPosition(hps, (POINTL *)&(lprc->xLeft));
     GpiBox(hps, DRO_OUTLINE, (POINTL *)&(lprc->xRight), 0L, 0L);
 }




 /************************************************************************
 *
 *   LfChar
 *
 *   Handle LineFractal's keyboard interface.  This consists of:
 *
 *   <space>        Cancels the selection rectangle.
 *   0-9         Brings thread i to the top, if it exists.
 *
 ************************************************************************/

 VOID
 LfChar(hwnd, ch)
 HWND hwnd;
 char ch;
 {
     HPS hps;
     PRECTL lprc;
     USHORT i;

     if (ch == ' ')
     {
         if (global.fShowSelectRc)
         {
             global.fShowSelectRc = FALSE;
             hps = WinGetPS(hwnd);
             if (global.fTempSelect)
             {
                 lprc = &global.rclSelect;
                 global.fTempSelect = FALSE;
             }
             else
                 lprc = &global.rclCutCopy;
             LfShowSelectRc(hps, lprc);
             WinReleasePS(hps);
         }
     }
     else if (ch >= '0' && ch <= '9')
     {
         i = ch - '0';
         if (i < global.cThr)
             LfBringThreadToTop(global.aThr[i]);
     }
 }




 /************************************************************************
 *
 *   LfMouseMove
 *
 *   Handle actions necessary upon each move of the mouse pointer.
 *   This consists of resetting the mouse pointer -- if we pass
 *   this on to WinDefWindowProc, it will reset it to the arrow.
 *   As long as we're setting the pointer so often, we can do a
 *   little animation.
 *
 ************************************************************************/

 VOID
 LfMouseMove()
 {
     /* Do this so that if in tracking mode, the correct pointer
        shape appears immediately. */
     if (global.fTracking || global.fSelecting)
     {
         if (global.hptrTrack)
             WinSetPointer(HWND_DESKTOP,global.hptrTrack);
         return;
     }
     if (global.fPasting)
     {
         if (global.hptrPaste)
             WinSetPointer(HWND_DESKTOP,global.hptrPaste);
         return;
     }
     if (global.pThrTop)
     {
          if (global.pThrTop->fBusy)
          {
             if (global.hptrWait)
                 WinSetPointer(HWND_DESKTOP,global.hptrWait);
             return;
          }
     }
     global.usPtrCounter = (global.usPtrCounter+1) % global.usPtrThreshold;
     if (global.usPtrCounter == 0)
     {
         global.usCurPtr += global.usPtrIncr;
         if (global.usCurPtr <= 0)
             global.usCurPtr = 0;
         else
             global.usCurPtr %= NUM_PTR_SHAPES;
         if ((global.usCurPtr == NUM_PTR_SHAPES - 1) ||
             (global.usCurPtr == 0))
             global.usPtrIncr *= -1;
     }
     if ((global.hptr)[global.usCurPtr])
         WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
 }




 /************************************************************************
 *
 *   LfButtonUp
 *
 *   Handle up clicks of the mouse buttons.  This consists of:
 *
 *   left button up        increase the depth of recursion
 *   right button up        decrease the depth of recursion
 *
 *                        In both cases, clear the semaphore so the
 *                        drawing can restart at the new level of
 *                        recursion.
 *
 ************************************************************************/

 VOID
 LfButtonUp(usMsg)
 USHORT usMsg;
 {
     if (global.fMouseChangesRecursion)
     {
         if (usMsg == WM_BUTTON1UP)
             global.pThrTop->usRecursion = ++global.pThrTop->usRecursion;
         else if (global.pThrTop->usRecursion > 0)
             global.pThrTop->usRecursion = --global.pThrTop->usRecursion;

         global.pThrTop->flMiscAttrs |= LFA_RECURSION;
         global.pThrTop->fUpdateAttrs = TRUE;
         global.fUpdateAttrs = TRUE;

         if (global.pThrTop->hps)
         {
             global.pThrTop->fInterrupted = TRUE;
             DosSemClear(&(global.pThrTop)->lSemRedraw);
         }
     }
 }




 /************************************************************************
 *
 *   LfButtonDown
 *
 *   Handle down clicks of the mouse buttons.  This consists of
 *   changing the mouse pointer depending upon which mode we're in
 *   and calling off to the subroutine to do the real work given
 *   the position at which the mouse button was clicked.  The modes
 *   are "tracking", "selecting", and "pasting".  Tracking means the
 *   user is defining the new dimensions of the fractal.  Selecting
 *   means the user is dragging a rectangle to cut or copy.  Pasting
 *   means the user is positioning the cut or copied rectangle in
 *   the window.
 *
 ************************************************************************/

 VOID
 LfButtonDown(hwnd, mp1)
 HWND hwnd;
 MPARAM mp1;
 {
     POINTS pt;

     if (global.fTracking)
     {
         if (global.hptrTrack)
             WinSetPointer(HWND_DESKTOP,global.hptrTrack);
         pt.x = LOUSHORT(mp1);
         pt.y = HIUSHORT(mp1);
         LfSelectDimension(hwnd, pt);
         global.fTracking = FALSE;
         if ((global.hptr)[global.usCurPtr])
             WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
         if (global.pThrTop->fAttrRedraw)
         {
             global.pThrTop->fInterrupted = TRUE;
             DosSemClear(&(global.pThrTop)->lSemRedraw);
         }
     }
     else if (global.fSelecting)
     {
         if (global.hptrTrack)
             WinSetPointer(HWND_DESKTOP,global.hptrTrack);
         pt.x = LOUSHORT(mp1);
         pt.y = HIUSHORT(mp1);
         LfSelect(hwnd, pt);
         global.fSelecting = FALSE;
         if ((global.hptr)[global.usCurPtr])
             WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
     }
     else if (global.fPasting)
     {
         if (global.hptrPaste)
             WinSetPointer(HWND_DESKTOP,global.hptrPaste);
         pt.x = LOUSHORT(mp1);
         pt.y = HIUSHORT(mp1);
         LfPaste(hwnd);
         global.fPasting = FALSE;
         if ((global.hptr)[global.usCurPtr])
             WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
     }
 }




 /************************************************************************
 *
 *   LfStartRedrawTimer
 *
 ************************************************************************/

 VOID
 LfStartRedrawTimer()
 {
     USHORT timeout;

     if (!global.fTimerOn)
     {
         timeout = (USHORT) ((rand()/32767.0) *
                   (global.usMaxTimerDelay  - global.usMinTimerDelay) +
                    global.usMinTimerDelay);
         WinStartTimer(global.hab, global.hwnd, IDT_AUTOSTARTREDRAW, timeout);
         global.fTimerOn = TRUE;
     }
 }




 /************************************************************************
 *
 *   LfStopRedrawTimer
 *
 ************************************************************************/

 VOID
 LfStopRedrawTimer()
 {
     WinStopTimer(global.hab, global.hwnd, IDT_AUTOSTARTREDRAW);
     global.fTimerOn = FALSE;
 }


 LINK.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MSNGR\LINK.C

 /****************************** MODULE Header ******************************\
 * Module Name:  link.c - Messenger application - link module
 *
 * Created: 8/1/89  sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #include "msngr.h"
 #include "string.h"

 extern HWND hwndMsngr;
 extern HPOINTER hptrLink;
 extern SHORT cyText;
 extern HAB hab;
 extern HSZ hszAppName;
 extern HSZ hszEmailName;
 extern NPUSERLIST gnpUL;
 extern PFNWP lpfnSysEFWndProc;   /* holds the system edit control proc */

 typedef struct _LINKDATA {
     NPUSERLIST pUserItem;
     USHORT state;
     HWND hwndTextIn;
     char **ppszText;
     char szOut[MAX_LINKSTR + 1];
     USHORT cLines;
     USHORT iTop;
 } LINKDATA;
 typedef LINKDATA *NPLINKDATA;

 PFNWP lpfnStaticWndProc;

 char **AllocTextIn(USHORT cLines);
 void FreeTextIn(char **ppsz, USHORT cLines);
 void AddTextIn(NPLINKDATA pLinkData, PSZ pszTextIn);
 MRESULT EXPENTRY MyTextInWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp



 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   This creates a link window with control data set up as dictated by the
 *   parameters given.  state incidates whether the window was created by
 *   user or ddeml action.
 *
 * History:      1/18/89     Created         sanfords
 \***************************************************************************/
 BOOL CreateLinkWindow(pUserItem, state)
 NPUSERLIST pUserItem;
 USHORT state;
 {
     NPLINKDATA pLinkData;


     if (pUserItem->hwndLink != 0) {
         WinDestroyWindow(pUserItem->hwndLink);
         pUserItem->hwndLink = 0;
     }

     if (!(pLinkData = (NPLINKDATA)WinAllocMem(hheap, sizeof(LINKDATA)))) {
         NotifyUser(SZMEMORYERROR);
         return(0);
     }

     pLinkData->pUserItem = pUserItem;
     pLinkData->state = state;
     pUserItem->hwndLink = WinLoadDlg(HWND_DESKTOP, hwndMsngr, LinkDlgProc,
             0L, IDD_LINK, (PVOID)pLinkData);

     if (!WinPostMsg(pUserItem->hwndLink, UM_LINK1, 0L, 0L)) {
         WinDestroyWindow(pUserItem->hwndLink);
         return(FALSE);
     }
     return(TRUE);
 }


 char **AllocTextIn(cLines)
 USHORT cLines;
 {
     char **ppsz, *psz;

     if (!(ppsz = (char **)WinAllocMem(hheap,
             cLines * (MAX_LINKSTR + 1 + sizeof(NPSZ)))))
         return(0);
     psz = (char *)(ppsz + cLines);
     while (cLines--) {
         ppsz[cLines] = psz;
         *psz = '\0';
         psz += MAX_LINKSTR + 1;
     }
     return(ppsz);
 }


 void FreeTextIn(ppsz, cLines)
 char **ppsz;
 USHORT cLines;
 {
     WinFreeMem(hheap, (NPBYTE)ppsz, cLines * (MAX_LINKSTR + 1 + sizeof(NPSZ))
 }


 void AddTextIn(pLinkData, pszTextIn)
 NPLINKDATA pLinkData;
 PSZ pszTextIn;
 {
     HPS hps;
     RECTL rcl;
     USHORT cchOut, cLines;
     CHAR ch;

     hps = WinGetPS(pLinkData->hwndTextIn);

     cLines = 0;
     WinQueryWindowRect(pLinkData->hwndTextIn, &rcl);
     while (*pszTextIn) {
         cchOut = WinDrawText(hps, -1, pszTextIn, &rcl, 0L, 0L,
                 DT_LEFT | DT_BOTTOM | DT_WORDBREAK | DT_QUERYEXTENT);
         ch = pszTextIn[cchOut];
         pszTextIn[cchOut] = '\0';
         lstrcpy(pLinkData->ppszText[pLinkData->iTop++], pszTextIn);
         if (pLinkData->iTop == pLinkData->cLines)
             pLinkData->iTop = 0;
         pszTextIn[cchOut] = ch;
         pszTextIn += cchOut;
         while (*pszTextIn == ' ')
            pszTextIn++;
         cLines++;
     }
     WinReleasePS(hps);
     WinQueryWindowRect(pLinkData->hwndTextIn, &rcl);
     WinScrollWindow(pLinkData->hwndTextIn, 0, cyText * cLines,
             NULL, &rcl, NULL, NULL, SW_INVALIDATERGN);
 }



 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   This is a state-driven dialog which allows user linking.  At createion
 *   time the control data is set to contain information on who this link
 *   is with, and why this window was created. (user action or another user
 *   requesting a link.
 *
 * History:      1/18/89     Created         sanfords
 \***************************************************************************/
 MRESULT EXPENTRY LinkDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     register NPLINKDATA pLinkData;
     register NPUSERLIST pUserItem;
     char szName[MAX_NAMESTR + 1];
     char szTitle[MAX_TITLESTR + 1];
     SWP swp;

     if (pLinkData = (NPLINKDATA)WinQueryWindowUShort(hwnd, QWS_USER))
         pUserItem = pLinkData->pUserItem;

     switch(msg) {
     case WM_INITDLG:
         /*
          * This is required because currently, automatic ICON resource loadin
          * is not supported for dialogs.
          */
         if (hptrLink == 0) {
             hptrLink = WinLoadPointer(HWND_DESKTOP, NULL, IDD_LINK);
         }
         WinSendMsg(hwnd, WM_SETICON, (MPARAM)hptrLink, 0L);

         /*
          * we are initialized with a (near) pointer to a LINKDATA structure.
          * Keep it in our window words.
          */
         pLinkData = (NPLINKDATA)(USHORT)mp2;
         WinSetWindowUShort(hwnd, QWS_USER, (USHORT)pLinkData);
         pLinkData->hwndTextIn = WinWindowFromID(hwnd, IDC_TEXTIN);

         /*
          * set the output window so it is a integral multiple of cyText
          * high.
          */
         WinQueryWindowPos(pLinkData->hwndTextIn, &swp);
         swp.cy -= swp.cy % cyText;
         pLinkData->cLines = swp.cy / cyText;
         pLinkData->iTop = 0;
         swp.fs = SWP_SIZE;
         WinSetMultWindowPos(hab, &swp, 1);

         /*
          * set up constants used for text output.
          */
         lpfnStaticWndProc = WinSubclassWindow(pLinkData->hwndTextIn,
                 MyTextInWndProc);
         pLinkData->cLines = swp.cy / cyText;
         pLinkData->ppszText = AllocTextIn(pLinkData->cLines);

         /*
          * subclass the edit control so that enter keys send us a message.
          */
         lpfnSysEFWndProc =
                 WinSubclassWindow(WinWindowFromID(hwnd, IDC_EF_TEXTOUT),
                 EnhancedEFWndProc);

         WinSendDlgItemMsg(hwnd, IDC_EF_TEXTOUT, EM_SETTEXTLIMIT,
                 MPFROMSHORT(MAX_LINKSTR), 0L);

         /*
          * return 1 so we don't get the focus - we are still invisible!
          */
         return(1);
         break;

     case WM_DESTROY:
         /*
          *      Terminate advise loop.
          *      Destroy connection if made
          *          advise loops first.
          *      free control data
          */
         if (pUserItem->hConvLink) {
             if (pLinkData->state == LNKST_LINKED) {
                 DdeClientXfer(0, 0L, pUserItem->hConvLink,
                         hszEmailName,
                         DDEFMT_TEXT, XTYP_ADVSTOP, ulTimeout, NULL);
             }
             DdeDisconnect(pUserItem->hConvLink);
             pUserItem->hConvLink = 0;
         }
         FreeTextIn(pLinkData->ppszText, pLinkData->cLines);
         WinFreeMem(hheap, (NPBYTE)pLinkData, sizeof(LINKDATA));
         pUserItem->hwndLink = 0;
         break;

     case UM_LINK1:
         /*
          * connect on link channel - topic = server name.
          */
         if (!pUserItem->hConvLink && !(pUserItem->hConvLink =
                 DdeConnect(hszAppName, pUserItem->hsz, NULL, (HAPP)mp1))) {
             if (pLinkData->state == LNKST_USERSTART)
                 NotifyUser(SZCANTLINK);
             return(FALSE);
         }

         if (pLinkData->state == LNKST_USERSTART) {
             return(WinSendMsg(hwnd, UM_DOADVSTART, 0L, 0L));
         } else {
             /*
              * We were started by a ddeml advise message - return now
              * and post an advise start message to ourselves so we send
              * it AFTER we returned from his advise.
              */
             WinPostMsg(hwnd, UM_DOADVSTART, 0L, 0L);
             return(TRUE);
         }
         break;

     case UM_DOADVSTART:
         /*
          * Its time to start the advise loop...return fSuccess
          *
          * Item = client name.
          */
         if (DdeClientXfer(0, 0L, pUserItem->hConvLink, hszEmailName,
                 DDEFMT_TEXT, XTYP_ADVSTART, ulTimeout, NULL)) {
             DdeGetHszString(hszEmailName, szName, MAX_NAMESTR + 1L);
             lstrcat(szTitle, szName, SZMSGLINKWITH);
             DdeGetHszString(pUserItem->hsz, szName, MAX_NAMESTR + 1L);
             lstrcat(szTitle, szTitle, szName);
             WinSetDlgItemText(hwnd, FID_TITLEBAR, szTitle);
             if (pLinkData->state == LNKST_DMGSTART) {
                 pLinkData->state = LNKST_LINKED;
                 WinSetDlgItemText(hwnd, IDC_INFO, SZLINKED);
             }
             WinShowWindow(hwnd, TRUE);
             return(TRUE);

         } else {
             if (pLinkData->state == LNKST_USERSTART)
                 MyPostError(DdeGetLastError());
             else
                 /*
                  * destroy ouselves lest we have an invisible link window
                  * hanging around.
                  */
                 WinDestroyWindow(hwnd);
             return(FALSE);
         }
         break;

     case UM_ADVRCVD:
         if (pLinkData->state == LNKST_USERSTART) {
             pLinkData->state = LNKST_LINKED;
             WinSetDlgItemText(hwnd, IDC_INFO, SZLINKED);
         }
         break;

     case UM_LINKDATAIN:
         AddTextIn(pLinkData, (PSZ)mp1);
         break;

     case UM_BREAKLINK:
         if (pLinkData->state == LNKST_LINKED) {
             NotifyUser(SZLINKBROKEN);
             if (WinIsWindow(hab, hwnd))
                 WinDestroyWindow(hwnd);
         }
         break;

     case WM_WINDOWPOSCHANGED:
         /*
          * hide the Edit control when minimized since it messes up the icon.
          */
         if ((LONG)mp2 & AWP_MINIMIZED)
             WinShowWindow(WinWindowFromID(hwnd, IDC_EF_TEXTOUT), FALSE);
         else if ((LONG)mp2 & AWP_RESTORED)
             WinShowWindow(WinWindowFromID(hwnd, IDC_EF_TEXTOUT), TRUE);
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDC_TERMINATE:
             if (pLinkData->state == LNKST_LINKED) {
                 pLinkData->state = LNKST_UNLINKED;
                 DdeClientXfer(0, 0L, pUserItem->hConvLink, hszEmailName,
                         DDEFMT_TEXT, XTYP_ADVSTOP, ulTimeout, NULL);
             }
             WinDestroyWindow(hwnd);
             break;
         }
         break;

     case ENHAN_ENTER:
         /*
          * when the user hits the enter key, it will be passed from the
          * entryfield to here and we will send it to the linkee.
          */
         WinQueryDlgItemText(hwnd, IDC_EF_TEXTOUT, MAX_LINKSTR + 1,
                 pLinkData->szOut);
         WinSetDlgItemText(hwnd, IDC_EF_TEXTOUT, "");
         if (pLinkData->state == LNKST_LINKED)
             /*
              * we postadvise to OUR server which is talking to HIS client
              * which has the topic being OUR name and the item being HIS.
              * This results in a UM_LINKDATAOUT being sent to us.
              */
             DdePostAdvise(hszEmailName, pUserItem->hsz);
         break;

     case UM_LINKDATAOUT:
         /*
          * returns hDmgData filled.
          */
         if (pLinkData->state == LNKST_LINKED) {
             return(DdePutData(pLinkData->szOut,
                     (ULONG)(lstrlen(pLinkData->szOut) + 1), 0L,
                     pUserItem->hsz, DDEFMT_TEXT, 0));
         }
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }




 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   This function gains control whenever externaly trigered data transfers
 *   are sent to the hszEmailName topic.
 *
 * History:      1/17/89     Created         sanfords
 \***************************************************************************/
 HDMGDATA LinkXfer(pXferInfo, hszPartner)
 PXFERINFO pXferInfo;
 HSZ hszPartner;
 {
     NPUSERLIST pUserItem;
     char szTextIn[MAX_LINKSTR + 1];

     /*
      * always allow creation of servers on this topic.
      */
     if (pXferInfo->usType == XTYP_INIT)
         return(TRUE);

     /*
      * we only allow text
      */
     if (pXferInfo->usFmt != DDEFMT_TEXT)
         return(0);

     /*
      * if he's gone, this should never happen.
      */
     if (!(pUserItem = FindUser(gnpUL, hszPartner))) {
         NotifyUser(SZINTERNALERROR);
         return(0);
     }

     if (pXferInfo->usType == XTYP_ADVSTART) {
         /*
          * link Advise startup - if no hwndLink, he started it, we must
          * create a link window and a counter advise loop.  If we do
          * have a hwndLink, we started it and it is his counter advise
          * link request.
          */
         if (!WinIsWindow(hab, pUserItem->hwndLink)) {
             return((HDMGDATA)CreateLinkWindow(pUserItem, LNKST_DMGSTART));
         } else {
             WinSendMsg(pUserItem->hwndLink, UM_ADVRCVD, 0L, 0L);
             return(DDE_FACK);
         }
     }

     /*
      * The rest of these are only applicable if we are in an active link
      * situation, so quit if no link window is present.
      */
     if (!WinIsWindow(hab, pUserItem->hwndLink))
         return(0);

     switch (pXferInfo->usType) {
     case XTYP_ADVDATA:
         /*
          * incomming link data
          */
         DdeGetData(pXferInfo->hDmgData, szTextIn, (ULONG)(MAX_LINKSTR + 1), 0
         DdeFreeData(pXferInfo->hDmgData);
         WinSendMsg(pUserItem->hwndLink, UM_LINKDATAIN, (MPARAM)szTextIn, 0L);
         return(DDE_FACK);
         break;

     case XTYP_REQUEST:
     case XTYP_ADVREQ:
         /*
          * Outgoing link data
          */
         return((HDMGDATA)WinSendMsg(pUserItem->hwndLink, UM_LINKDATAOUT, 0L,
         break;

     case XTYP_ADVSTOP:
         WinSendMsg(pUserItem->hwndLink, UM_BREAKLINK, 0L, 0L);
         return(TRUE);
         break;
     }
     return(0);
 }



 MRESULT EXPENTRY MyTextInWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     register NPLINKDATA pLinkData;
     RECTL rcl;
     HPS hps;
     USHORT iLine, cLines;

     switch (msg) {
     case WM_PAINT:
         hps = WinBeginPaint(hwnd, NULL, &rcl);
         pLinkData = (NPLINKDATA)WinQueryWindowUShort(
                 WinQueryWindow(hwnd, QW_PARENT, FALSE), QWS_USER);
         WinQueryWindowRect(hwnd, &rcl);
         rcl.yBottom = rcl.yTop - cyText;

         iLine = pLinkData->iTop;
         cLines = pLinkData->cLines;
         while (cLines--) {
             WinDrawText(hps, -1, pLinkData->ppszText[iLine++], &rcl, 0L, 0L,
                     DT_LEFT | DT_VCENTER | DT_ERASERECT | DT_TEXTATTRS);
             if (iLine == pLinkData->cLines)
                 iLine = 0;
             WinOffsetRect(hab, &rcl, 0, -cyText);
         }
         WinEndPaint(hps);
         break;

     default:
         return((*lpfnStaticWndProc)(hwnd, msg, mp1, mp2));
     }
 }



 MDI.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\MDI.C

 /***************************************************************************\
 * mdi.c - MDI application
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINHEAP

 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"

 VOID MDIDesktopSize(HWND hwnd, MPARAM mp1, MPARAM mp2)
 {
     HWND hwndDoc;
     RECTL rclWindow;
     POINTL ptlBorderSize;
     HENUM henum;
     SWP swp;
     ULONG ulStyle;

     /*
      * This code keeps maximized MDI windows maximized within the
      * main client window and bottom-left aligns minimized windows.
      */
     WinQueryWindowRect(hwnd, (PRECTL)&rclWindow);

     WinSendMsg(hwndMDIFrame, WM_QUERYBORDERSIZE, (MPARAM)&ptlBorderSize,
             0L);
     WinInflateRect(NULL, (PRECTL)&rclWindow, (SHORT)ptlBorderSize.x,
             (SHORT)ptlBorderSize.y);
     rclWindow.yTop += cyTitlebar;

     swp.hwndInsertBehind = NULL;

     henum = WinBeginEnumWindows(hwnd);
     while (hwndDoc = WinGetNextWindow(henum)) {
         WinLockWindow(hwndDoc, FALSE);
         ulStyle = WinQueryWindowULong(hwndDoc, QWL_STYLE);
         if (ulStyle & WS_MAXIMIZED) {
             swp.hwnd = hwndDoc;
             swp.x = (SHORT)rclWindow.xLeft;
             swp.y = (SHORT)rclWindow.yBottom;
             swp.cx = (SHORT)rclWindow.xRight - (SHORT)rclWindow.xLeft;
             swp.cy = (SHORT)rclWindow.yTop - (SHORT)rclWindow.yBottom;
             swp.fs = SWP_MOVE | SWP_SIZE;
             WinSetMultWindowPos(NULL, (PSWP)&swp, 1);
         } else if (ulStyle & WS_MINIMIZED) {

             WinQueryWindowPos(hwndDoc, &swp);

             swp.x = 0;
             swp.y = 0;
             swp.fs = SWP_MOVE | SWP_ZORDER;
             swp.hwndInsertBehind = HWND_BOTTOM;

        /*
             swp.y -= (SHORT2FROMMP(mp2) - SHORT2FROMMP(mp1));
             swp.fs = SWP_MOVE;
        */
             WinSetMultWindowPos(NULL, (PSWP)&swp, 1);

         }
     }
     WinEndEnumWindows(henum);

     /*
      * Adjust yNextNewDoc to keep it top-left aligned.
      */
     yNextNewDoc += (SHORT2FROMMP(mp2) - SHORT2FROMMP(mp1));
 }


 VOID MDIDesktopSetFocus(HWND hwnd, MPARAM mp2)
 {
     HWND hwndTopDoc;

     /*
      * If we're getting the focus and there is a document window,
      * set the focus to the top-most document window so the main
      * client window never has the focus.
      */
     if ((SHORT1FROMMP(mp2) != FALSE) &&
             (hwndTopDoc = WinQueryWindow(hwnd, QW_TOP, FALSE))) {
         WinSetFocus(HWND_DESKTOP, hwndTopDoc);
     }
 }


 VOID MDIDesktopActivateDoc(SHORT idMenuitem)
 {
     register NPDOC npdoc;

     npdoc = npdocFirst;
     while (npdoc != NULL) {
         if (npdoc->idMI == (USHORT) idMenuitem) {
             WinSetFocus(HWND_DESKTOP, npdoc->hwndFrame);

             /*
              * If the document is minimized then
              * restore it as well.
              */
             if (WinQueryWindowULong(npdoc->hwndFrame, QWL_STYLE) &
                     WS_MINIMIZED) {
                 WinSetWindowPos(npdoc->hwndFrame, NULL,
                         0, 0, 0, 0, SWP_RESTORE);
             }
             break;
         }
         npdoc = npdoc->npdocNext;
     }
 }

 MRESULT EXPENTRY MainFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1,
         MPARAM mp2)
 {
     HWND hwndAppMenu;
     NPDOC npdoc;

     switch (msg) {

     case WM_NEXTMENU:
         /*
          * Connect child sysmenu with application menus
          */

         npdoc=NPDOCFROMCLIENT(WinWindowFromID(hwndActiveDoc, FID_CLIENT));

         if (npdoc->fAabSysMenu == TRUE) {
             /* child sysmenu is already in app menu */
             return (*pfnMainFrameWndProc)(hwnd, msg, mp1, mp2);
             }

 #define hwndCurrent HWNDFROMMP(mp1)

         hwndAppMenu=NPDOCFROMCLIENT(WinWindowFromID(hwndActiveDoc, FID_CLIENT
                         ->hwndMainMenu;

         if (((hwndCurrent == hwndSysMenu) && !SHORT1FROMMP(mp2)) ||
                 ((hwndCurrent == hwndAppMenu) && SHORT1FROMMP(mp2))) {

             if (hwndActiveDoc)
                 return (WinWindowFromID(hwndActiveDoc, FID_SYSMENU));
         }

 #undef hwndCurrent

         break;
     }
     return (*pfnMainFrameWndProc)(hwnd, msg, mp1, mp2);
 }


 MDIDATA.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\MDIDATA.C

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINACCELERATORS
 #define INCL_WINMENUS
 #define INCL_WINHEAP
 #define INCL_WINPOINTERS

 #include <os2.h>
 #include "app.h"
 #include "mdi.h"

 char szSplitbarClass[] = "PM MDI Splitbar";
 USHORT usDocNumber = 0, cDocs = 0;
 USHORT cxVertSplitbar, cyHorzSplitbar, cxSplitbarCenter, cySplitbarCenter;
 SHORT cxNewDoc, cyNewDoc, xCascadeInc, yCascadeInc;
 SHORT xNextNewDoc, yNextNewDoc;
 SHORT xCascadeSlot;
 SHORT cyTitlebar, cxMinmaxButton;

 /* Frame subclassing globals */
 PFNWP pfnFrameWndProc;
 PFNWP pfnMainFrameWndProc;

 /* Main MDI globals */
 HDC hdcMem;
 HPS hpsMem;
 HPOINTER hptrHorzSplit, hptrVertSplit, hptrHVSplit, hptrArrow;

 /* Menu globals */
 HBITMAP hbmChildSysMenu, hbmAabChildSysMenu;
 MENUITEM miAabSysMenu;
 HWND hwndFirstMenu;


 MDIDOC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\MDIDOC.C

 /***************************************************************************\
 * mdidoc.c - MDI application
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINFRAMEMGR
 #define INCL_WINPOINTERS
 #define INCL_WINMENUS
 #define INCL_WINWINDOWMGR
 #define INCL_WINACCELERATORS
 #define INCL_WININPUT
 #define INCL_WINHEAP
 #define INCL_WINSCROLLBARS
 #define INCL_WINRECTANGLES
 #define INCL_WINCOUNTRY
 #define INCL_WINDIALOGS
 #define INCL_GPIPRIMITIVES
 #define INCL_GPILOGCOLORTABLE

 #include <os2.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"

 BOOL fDestroying=FALSE;

 /* Function Prototypes */
 VOID BuildWindowMenu(VOID);
 VOID TrackSplitbars(HWND, USHORT, SHORT, SHORT);
 MRESULT MDIFormatFrame(HWND hwnd, PSWP aswp, MPARAM mp2);
 MRESULT MDIMinMaxFrame(HWND hwnd, MPARAM mp1, MPARAM mp2);

 VOID SetMainTitleText(HWND hwndDocFrame)
 {
     char szDocTitle[80], szMainTitle[80];

     /*
      * Get the titlebar text for the specified document window.
      */
     WinQueryWindowText(hwndDocFrame, 80, (PSZ)szDocTitle);

     /*
      * Build up the correct text for the main window.
      */
     strcpy(szMainTitle, " - ");
     strcat(szMainTitle, szDocTitle);

     /*
      * Set the main window's titlebar to the new text.
      */
     WinSetWindowText(hwndMDIFrame, szMainTitle);
 }


 VOID ClearMainTitleText(VOID)
 {
     WinSetWindowText(hwndMDIFrame, "");
 }


 /* *********************************************************************** *\
  * AddToWindowMenu
  *
  * This routines adds a new document to the WINDOW submenu of each document's
  * menu so that when any doc is active you can select the other documents
  * by selecting it on the main menu bar
  *
  * History:
  *
  *      19-Oct-1989 Added multiple menu capabilities -johnba
  *
 \* *********************************************************************** */

 /* MULTIPLEMENU */

 VOID AddToWindowMenu(NPDOC npdocNew)
 {
     MENUITEM mi,miDoc;
     char szItemText[1];
     char szTitleNew[128], szTitleCompare[128];
     register NPDOC npdoc, npdocPrev;
     USHORT usRes;

     HWND hwndWindowMenu;
     USHORT index;


     /*
      * Add the document to the DOCLIST.
      */
     if (npdocFirst == NULL) {
         npdocFirst = npdocNew;

         /*
          * Fill in the DOCLIST for this doc.
          */
         npdocNew->idMI = NULL;
         npdocNew->npdocNext = NULL;
         }

     else {

         /*
          * Add the item in the correct
          * sorted location.
          */
         npdoc = npdocFirst;
         npdocPrev = NULL;

         while (npdoc != NULL) {
             WinQueryWindowText(npdoc->hwndFrame, 128, szTitleCompare);
             WinQueryWindowText(npdocNew->hwndFrame, 128, szTitleNew);
             WinUpper(NULL, NULL, NULL, szTitleCompare);
             WinUpper(NULL, NULL, NULL, szTitleNew);

             usRes = WinCompareStrings(NULL, NULL, NULL, szTitleNew,
                     szTitleCompare, NULL);

             if (usRes == WCS_LT) {
                 if (npdocPrev == NULL) {
                     npdocFirst = npdocNew;
                     npdocFirst->npdocNext = npdoc;
                     }
                 else {
                     npdocPrev->npdocNext = npdocNew;
                     npdocNew->npdocNext = npdoc;
                     }

                 /*
                  * Fill in the DOC for this document.
                  */
                 npdocNew->idMI = NULL;
                 break;
                 }
             else {
                 if (npdoc->npdocNext == NULL) {
                     /*
                      * If we've made it to the end,
                      * just tack it on here.
                      */
                     npdoc->npdocNext =  npdocNew;

                     /*
                      * Fill in the DOC for this document.
                      */
                     npdocNew->idMI = NULL;
                     npdocNew->npdocNext = NULL;
                     }
                 }
             npdocPrev = npdoc;
             npdoc = npdoc->npdocNext;
             }

         }


 /*  Add menu items to the new doc's menu to make all doc's window menus
     have the same number of docs before this one was added */

     /* Get the hwnd to the IDM_WINDOW submenu for this npdoc's menu bar */
     WinSendMsg(npdocNew->hwndMainMenu, MM_QUERYITEM,
                 MPFROM2SHORT(IDM_WINDOW, FALSE), MPFROMP(&mi));
     hwndWindowMenu = mi.hwndSubMenu;

     /* build blank menuitem structure for all documents */

     mi.iPosition = MIT_END;
     mi.afStyle = MIS_TEXT;
     mi.afAttribute = 0;
     mi.hwndSubMenu = NULL;
     mi.hItem = NULL;
     szItemText[0] = '\0';

     for(index=1; index<=cDocs; index++) {

         mi.id = CMD_WINDOWITEMS + index;

         WinSendMsg(hwndWindowMenu, MM_INSERTITEM, MPFROMP(&mi),
                 MPFROMP(szItemText));
         }

     cDocs++;

     /* Now all the document window menus are similiar so we can now add the
        new menu to each of them */

     /* build menuitem structure for this document */
     miDoc.iPosition = MIT_END;
     miDoc.afStyle = MIS_TEXT;
     miDoc.afAttribute = 0;
     miDoc.id = CMD_WINDOWITEMS + cDocs;
     miDoc.hwndSubMenu = NULL;
     miDoc.hItem = NULL;
     szItemText[0] = '\0';

     npdoc=npdocFirst;

     /* insert a blank item into EVERY document's menu */
     while (npdoc!=NULL) {

         /* This gets the hwnd to the IDM_WINDOW submenu for this npdoc's
            menu bar. */

         WinSendMsg(npdoc->hwndMainMenu, MM_QUERYITEM,
                     MPFROM2SHORT(IDM_WINDOW, FALSE), MPFROMP(&mi));
         hwndWindowMenu = mi.hwndSubMenu;

         /*
          * Insert a blank item into the menu.
          */

         WinSendMsg(hwndWindowMenu, MM_INSERTITEM, MPFROMP(&miDoc),
                 MPFROMP(szItemText));

         npdoc=npdoc->npdocNext;
         }

     npdocNew->idMI=miDoc.id; /* Set the ID for the menu item of the new docum

     BuildWindowMenu();
 }


 VOID RemoveFromWindowMenu(NPDOC npdocRemove)
 {
     register NPDOC npdoc, npdocPrev;

     MENUITEM mi;

     HWND hwndWindowMenu;


     npdoc=npdocFirst;

     while (npdoc!=NULL) {

         /* This gets the hwnd to the IDM_WINDOW submenu for this npdoc's
            menu bar. */

         /*
          * If the window menu is gone no need to
          * do any of this stuff.  This can happen
          * when the MDI app is closing.
          */

         if (WinIsWindow(hab, npdoc->hwndMainMenu)) {


             WinSendMsg(npdoc->hwndMainMenu, MM_QUERYITEM,
                         MPFROM2SHORT(IDM_WINDOW, FALSE), MPFROMP(&mi));
             hwndWindowMenu = mi.hwndSubMenu;

             /* Delete item from menu */
             WinSendMsg(hwndWindowMenu, MM_DELETEITEM,
                     MPFROM2SHORT(npdocRemove->idMI, FALSE), NULL);

             }
         npdoc=npdoc->npdocNext;
         }

     /*
      * Remove the DOC from the linked-list.
      */
     if (npdocFirst != NULL) {

         /*
          * Initialize these for our while loop.
          */
         npdoc = npdocFirst;
         npdocPrev = NULL;

         while (npdoc != NULL) {

             /*
              * If we've found the element unlink
              * it from the list.
              */
             if (npdoc == npdocRemove) {

                 /*
                  * Unlink this document.
                  */
                 if (npdocPrev != NULL)
                     npdocPrev->npdocNext = npdoc->npdocNext;
                 else
                     npdocFirst = npdoc->npdocNext;

                 /*
                  * Break out of the while loop,
                  * we're done here.
                  */
                 break;

            /*   cDocs--;  */  /* ?????????????????  this will help the menus
                              sort of.  It will cause problems when we try
                              to reuse the same ID again in a new document win
                              ?????????????????????
                           */

             }
             npdocPrev = npdoc;
             npdoc = npdoc->npdocNext;
         }
     }

     /* If we are terminating the app, don't try to build the menu since it
         will make a mess */

     if (!fDestroying) {
         BuildWindowMenu();
         }
 }


 /* *********************************************************************** *\
  * BuildWindowMenu
  *
  * This routine fills in the IDM_WINDOW submenu items with the titles of
  * all the document windows.  It uses the first doc's submenu as a base to
  * determine the ID's of the MENUITEMs and then changes the in the IDM_WINDOW
  * submenu off ALL the document's menu bars.
  *
  * History:
  *
  *      19-Oct-1989 Added multiple menu capabilities -johnba
  *
 \* *********************************************************************** */

 /* MULTIPLEMENU */

 VOID BuildWindowMenu(VOID)
 {
     register NPDOC npdoc;
     NPDOC npdocSetItem;
     char szDocTitle[80];
     char szItemText[80];
     USHORT cWindows;

     /*
      * Get the position of the first
      * window in the Window menu.
      */

     MENUITEM mi;
     HWND hwndFirstWindowMenu;
     HWND hwndMenu;

     if (npdocFirst==NULL) {
         WinSetParent(hwndFirstMenu, hwndMDIFrame, FALSE);
         WinSendMsg(hwndMDIFrame, WM_UPDATEFRAME, 0L, 0L);
         return;
         }


     /* first query the hwnd of the IDM_WINDOW submenu which will be used
        to determine the ID's of all the IDM_WINDOW subitems. */
     hwndMenu = npdocFirst->hwndMainMenu;
     WinSendMsg(hwndMenu, MM_QUERYITEM, MPFROM2SHORT(IDM_WINDOW, FALSE),
                MPFROMP(&mi));
     hwndFirstWindowMenu = mi.hwndSubMenu;

     npdoc = npdocFirst;
     cWindows = 1;

     /* scan the documents to get the text of each of their title bars and
        build the item text for each documents entry in the submenu */
     while (npdoc != NULL) {

         /*
          * Build the item string.
          */
         szItemText[0] = '~';
         itoa(cWindows, szItemText + 1, 10);
         strcat(szItemText, " ");
         WinQueryWindowText(npdoc->hwndFrame, 80, (PSZ)szDocTitle);
         strcat(szItemText, szDocTitle);


         /* Now for each item loop through the documents, setting the text of
            this menu item for each of their submenu's */
         npdocSetItem=npdocFirst;

         while (npdocSetItem !=NULL) {

             HWND hwndWindowMenu;

             hwndMenu = npdocSetItem->hwndMainMenu;
             WinSendMsg(hwndMenu, MM_QUERYITEM,
                         MPFROM2SHORT(IDM_WINDOW, FALSE),
                         MPFROMP(&mi));
             hwndWindowMenu = mi.hwndSubMenu;


             /*
              * Set the item text for the menuitem.
              */
             WinSendMsg(hwndWindowMenu, MM_SETITEMTEXT,
                     MPFROMSHORT(npdoc->idMI),
                     MPFROMP(szItemText));

             /*
              * Clear out the previous attributes.
              */
             WinSendMsg(hwndWindowMenu, MM_SETITEMATTR,
                         MPFROM2SHORT(npdoc->idMI, FALSE),
                         MPFROM2SHORT(MIA_CHECKED, 0));

             npdocSetItem=npdocSetItem->npdocNext;
             }

         cWindows++;
         npdoc = npdoc->npdocNext;
     }
 }


 NPDOC MdiNewDocument(USHORT fsStyle, PSZ pszClassName)
 {
     ULONG ctlData, flStyle;
     HWND hwndNewFrame, hwndNewClient, hwndSysMenu;
     HWND hwndMenu;          /* added to handle different menus for each doc
                                    17-Oct-1989 */
     register NPDOC npdocNew;
     register NPVIEW npview;
     char szDocTitle[80], szDocNumber[4];

     /*  make sure there's no current menu on the frame */
     WinSetParent(WinWindowFromID(hwndMDIFrame, FID_MENU), HWND_OBJECT, FALSE)

     usDocNumber++;

     /*
      * Setup the window's titlebar text.
      */
     itoa(usDocNumber, szDocNumber, 10);
     strcpy(szDocTitle, "Untitled");
     strcat(szDocTitle, szDocNumber);

     ctlData = FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER |
             FCF_VERTSCROLL | FCF_HORZSCROLL;

     hwndNewFrame = WinCreateStdWindow(hwndMDI,
             FS_ICON | FS_ACCELTABLE,
             (VOID FAR *)&ctlData,
             pszClassName, szDocTitle,
             WS_VISIBLE,
             (HMODULE)NULL, IDR_MDIDOC,
             (HWND FAR *)&hwndNewClient);

 /* MULTIPLEMENU */

     switch (cDocs) {
         case 0:  /* This is the first document */
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOC1MENU);
             break;

         case 1:
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOC2MENU);
             break;

         case 2:
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOC3MENU);
             break;

         case 3:
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOC4MENU);
             break;

         case 4:
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOC5MENU);
             break;

         default:
             hwndMenu=WinLoadMenu(hwndMDIFrame, NULL, IDR_DOCXMENU);
             break;
         }

     /* The menu needs to be owned by the frame so that its WM_COMMAND message
        will get sent to the frame to be processed.
     */

     WinSetParent(hwndMenu, HWND_OBJECT, FALSE);

     npdocNew = (NPDOC)WinAllocMem(hHeap, sizeof(DOC));
     if (npdocNew == NULL)
         return (FALSE);

     npdocNew->hwndFrame = hwndNewFrame;
 /* MULTIPLEMENU */
     npdocNew->hwndMainMenu = hwndMenu;              /* new for multiple menus
     npdocNew->fAabSysMenu=FALSE;

     npdocNew->cxVertSplitPos = 0;
     npdocNew->cyHorzSplitPos = 0;
     npdocNew->fs = 0;
     npdocNew->fsStyle = fsStyle;

     npview = NPVIEWFROMCLIENT(hwndNewClient);
     npview->npdoc = npdocNew;

     /*
      * Link in the VIEW for FID_CLIENT since we
      * know it exists.
      */
     npdocNew->npviewFirst = npview;

     /*
      * Create the 'splitbar' controls for the frame.
      * This includes the splitbar and the extra client
      * and scrollbar windows.  This routine will also
      * link the appropriate VIEWs into the DOC structure.
      */
     if (fsStyle & (DS_HORZSPLITBAR | DS_VERTSPLITBAR)) {
         if (CreateSplitbarWindows(hwndNewFrame, npdocNew) == FALSE) {
             WinDestroyWindow(hwndNewFrame);
             return (FALSE);
         }
     }

     /*
      * Load in the document window's system menu.
      */
     hwndSysMenu = WinLoadMenu(hwndNewFrame, (HMODULE)NULL, IDM_DOCSYSMENU);

     /*
      * Make it look like a normal system menu to the frame manager so
      * gets formatted correctly etc.
      */
     flStyle = WinQueryWindowULong(hwndSysMenu, QWL_STYLE);
     WinSetWindowULong(hwndSysMenu, QWL_STYLE, flStyle | MS_TITLEBUTTON);
     WinSetWindowUShort(hwndSysMenu, QWS_ID, FID_SYSMENU);

     /*
      * Set the bitmap to the SBMP_CHILDSYSMENU bitmap.
      */
     WinSendMsg(hwndSysMenu, MM_SETITEMHANDLE, (MPARAM)SC_DOCSYSMENU,
             (MPARAM)hbmChildSysMenu);

     /*
      * Set the menu window handles in the DOC structure so these
      * controls can be hidden/shown at the correct time.
      */
     npdocNew->hwndSysMenu = hwndSysMenu;
     npdocNew->hwndMinmax = WinWindowFromID(hwndNewFrame, FID_MINMAX);

     /*
      * Subclass the frame so we can handle the accelerators
      * and other MDI stuff.
      */

     pfnFrameWndProc = WinSubclassWindow(hwndNewFrame,
             (PFNWP)DocFrameWndProc);
     /*
      * Add the window to the Window menu.
      */

  /*    AddToWindowMenu(npdocNew);
  */
     return (npdocNew);
 }


 VOID MDISetInitialDocPos(HWND hwndNewFrame)
 {
     WinSetWindowPos(hwndNewFrame, NULL, xNextNewDoc, yNextNewDoc,
             cxNewDoc, cyNewDoc, SWP_MOVE | SWP_SIZE | SWP_SHOW);

     /*
      * Insert logic to change xNextNewDoc/yNextNewDoc and cxNewDoc/cyNewDoc.
      */
 }

 /* ********************************************************************** *\
  * AddAabSysMenu - adds the sys menu to the menu bar for the given        *
                    document frame window.

    History:
     20-Oct-1989 modified to pass an hwnd of the doc frame to add
                 the menu to. -johnba

 \* ********************************************************************** */

 VOID AddAabSysMenu(HWND hwnd)
 {
     HWND hwndMenu;
     NPDOC npdoc;

     npdoc=NPDOCFROMCLIENT(WinWindowFromID(hwnd, FID_CLIENT));
     hwndMenu=npdoc->hwndMainMenu;

     if (npdoc->fAabSysMenu == FALSE) {
         WinSendMsg(hwndMenu, MM_INSERTITEM, MPFROMP(&miAabSysMenu),
                 (MPARAM)NULL);
         npdoc->fAabSysMenu = TRUE;
     }
 }

 /* ********************************************************************** *\
  * RemoveAabSysMenu - removes the sys menu from the menu bar for the given
                       document frame window.

    History:
     20-Oct-1989 modified to pass an hwnd of the doc frame to remove
                 the menu from. -johnba

 \* ********************************************************************** */




 VOID RemoveAabSysMenu(HWND hwnd)
 {
     HWND hwndMenu;
     NPDOC npdoc;

     npdoc=NPDOCFROMCLIENT(WinWindowFromID(hwnd, FID_CLIENT));
     hwndMenu=npdoc->hwndMainMenu;

     if (npdoc->fAabSysMenu == TRUE) {
         WinSendMsg(hwndMenu, MM_REMOVEITEM,
                 MPFROMSHORT(IDM_AABDOCSYSMENU), (MPARAM)FALSE);
         npdoc->fAabSysMenu = FALSE;
     }
 }


 BOOL MDICreate(HWND hwndClient)
 {
     register NPVIEW npview;

     /*
      * Allocate the DOC structure for this window and stick it in
      * the window structure of the client.
      */

     npview = (NPVIEW)WinAllocMem(hHeap, sizeof(VIEW));
     if (npview == NULL)
         return (FALSE);

     npview->xOrigin = 0;
     npview->yOrigin = 0;
     npview->fs = 0;
     npview->npviewNext = NULL;
     npview->hwndClient = hwndClient;

     WinSetWindowUShort(hwndClient, QWS_USER, (USHORT)npview);

     return (TRUE);
 }


 VOID MDIActivate(HWND hwndClient, BOOL fActivate)
 {
     HWND hwndFrame;
     register NPDOC npdoc;


     MENUITEM mi;
     HWND hwndWindowMenu;
     HWND hwndNewMenu;
     HWND hwndOldMenu;


     /*
      * If our active status is changing, show/hide the AAB Sysmenu, check
      * the correct item on the Window menu, and show/hide the titlebar
      * frame controls.  Also change the main menu bar  17-Oct-1989 johnba
      */

     hwndFrame = WinQueryWindow(hwndClient, QW_PARENT, FALSE);

     npdoc = NPDOCFROMCLIENT(hwndClient);
     if (fActivate != FALSE) {

         /* Receiving activation */

         hwndOldMenu = WinWindowFromID(hwndMDIFrame, FID_MENU);

         if (hwndOldMenu!=NULL) {

             WinSendMsg(hwndOldMenu, MM_QUERYITEM, MPFROM2SHORT(IDM_WINDOW, FA
                        MPFROMP(&mi));
             hwndWindowMenu = mi.hwndSubMenu;
             }

         hwndNewMenu=NPDOCFROMCLIENT(hwndClient)->hwndMainMenu;

         if (hwndOldMenu!=NULL) {
             WinSendMsg(hwndOldMenu, MM_REMOVEITEM,
                     MPFROMSHORT(IDM_AABDOCSYSMENU),
                     (MPARAM)FALSE);

             /* hide current menu */
             WinSetParent(hwndOldMenu, HWND_OBJECT, FALSE);

             /*
              * Uncheck the appropriate item on the Window menu.
              */
             WinSendMsg(hwndWindowMenu, MM_SETITEMATTR,
                     MPFROM2SHORT(npdoc->idMI, FALSE),
                     MPFROM2SHORT(MIA_CHECKED, 0));
             }


         /* put new menu on main frame window */
         WinSetParent(hwndNewMenu, hwndMDIFrame, FALSE);
         WinSendMsg(hwndMDIFrame, WM_UPDATEFRAME, 0L, 0L);

         if (WinQueryWindowULong(hwndFrame, QWL_STYLE) & WS_MAXIMIZED) {
             WinSendMsg(hwndNewMenu, MM_INSERTITEM,
                     MPFROMP(&miAabSysMenu),
                     (MPARAM)NULL);
             }


         WinSendMsg(hwndNewMenu, MM_QUERYITEM, MPFROM2SHORT(IDM_WINDOW, FALSE)
                    MPFROMP(&mi));
         hwndWindowMenu = mi.hwndSubMenu;

         hwndActiveDoc = hwndFrame; /* remember the active window in a global

         /*
          * Check the appropriate item on the Window menu.
          */
         WinSendMsg(hwndWindowMenu, MM_SETITEMATTR,
                 MPFROM2SHORT(npdoc->idMI, FALSE),
                 MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));

         /*
          * Show the titlebar frame controls.
          */

         WinSetParent(npdoc->hwndSysMenu, hwndFrame, FALSE);
         WinSetParent(npdoc->hwndMinmax, hwndFrame, FALSE);

         WinSendMsg(hwndFrame, WM_UPDATEFRAME, 0L, 0L);

     } else {
         /* Losing activation */


         /*
          * Remove the titlebar frame controls.
          */

         /* Need to hide them before setting the parent to HWND_OBJECT so
            that they get painted right when they are restored. 02-Aug-1989 */

         WinShowWindow(npdoc->hwndSysMenu, FALSE);
         WinShowWindow(npdoc->hwndMinmax, FALSE);

         WinSetParent(npdoc->hwndSysMenu, HWND_OBJECT, FALSE);
         WinSetParent(npdoc->hwndMinmax, HWND_OBJECT, FALSE);

         WinSendMsg(hwndFrame, WM_UPDATEFRAME, 0L, 0L);
     }
 }


 VOID MDIDestroy(HWND hwndClient)
 {

     WinFreeMem(hHeap, (NPBYTE)WinQueryWindowUShort(hwndClient, QWS_USER),
             sizeof(VIEW));
 }


 VOID MDIClose(HWND hwndClient)
 {
     WinDestroyWindow(WinQueryWindow(hwndClient, QW_PARENT, FALSE));
 }

 struct ARECTL {
     RECTL rclSrc;
     RECTL rclDest;
     };



 MRESULT EXPENTRY DocFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
 {
     MRESULT mres;
     USHORT cFrameCtls;
     HWND hwndParent, hwndClient;
     register NPDOC npdoc;
     RECTL rclClient;

 struct ARECTL far *prcl;

     PSWP    pswp;

     switch (msg) {

     case WM_ADJUSTWINDOWPOS:
         return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
         break;

     case WM_SYSCOMMAND:
         if (SHORT1FROMMP(mp1) == SC_SPLIT) {
             WinSetPointer(HWND_DESKTOP, hptrHVSplit);
             TrackSplitbars(WinWindowFromID(hwnd, FID_CLIENT),
                     SPS_HORZ | SPS_VERT, -1, -1);
             WinSetPointer(HWND_DESKTOP, hptrArrow);
         } else if (SHORT1FROMMP(mp2) == (SHORT) CMDSRC_ACCELERATOR) {

             /*
              * If the command was sent because of an accelerator
              * we need to see if it goes to the document or the main
              * frame window.
              */
             if ((WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000)) {

                 /*
                  * If the control key is down we'll send it
                  * to the document's frame since that means
                  * it's either ctl-esc or one of the document
                  * window's accelerators.
                  */
                 return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
             } else if (SHORT1FROMMP(mp1) == SC_DOCSYSMENU) {

                 /*
                  * If the window is maximized then we want
                  * to pull down the system menu on the main
                  * menu bar.
                  */
                 if ((WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED) &&
                         (SHORT1FROMMP(mp1) == SC_DOCSYSMENU)) {
                     WinPostMsg(miAabSysMenu.hwndSubMenu, MM_STARTMENUMODE,
                             MPFROM2SHORT(TRUE, FALSE), 0L);
                     return (0L);
                 } else {
                     WinPostMsg(WinWindowFromID(hwnd, FID_SYSMENU),
                             MM_STARTMENUMODE, MPFROM2SHORT(TRUE, FALSE), 0L);
                 }
             } else {
                 /*
                  * Control isn't down so send it the main
                  * frame window.
                  */
                 return WinSendMsg(hwndMDIFrame, msg, mp1, mp2);
             }
         } else {
             /*
              * WM_SYSCOMMAND not caused by an accelerator
              * so hwnd is the window we want to send the
              * message to.
              */
             return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
         }
         break;

     case WM_NEXTMENU:
         /*
          * Connect child sysmenu with application menus for cursor motion.
          * Only return a value if we're the System Menu.  We don't want the
          * MinMax menu or any others that might be there to freak out.
          */
         if (WinQueryWindowUShort(HWNDFROMMP(mp1), QWS_ID) == FID_SYSMENU) {
             if (SHORT1FROMMP(mp2)) {
                 return (MRESULT)hwndSysMenu;
                 }
             else {
                 /* return (MRESULT) hwndAppMenu; */
                 return (MRESULT) (NPDOCFROMCLIENT(WinWindowFromID(hwnd, FID_C
                                         ->hwndMainMenu);
                 }
         } else {
             return (0L);
         }
         break;

     case WM_MINMAXFRAME:
         return (MDIMinMaxFrame(hwnd, mp1, mp2));
         break;

     case WM_WINDOWPOSCHANGED:

 #define aswp    ((PSWP)mp1)

         npdoc = NPDOCFROMCLIENT(WinWindowFromID(hwnd, FID_CLIENT));
         if (((PSWP)mp1)->fs & SWP_SIZE) {

             if (npdoc->fs & (DF_SPLITHORZ | DF_HSPLITOVERFLOW)) {
                 /*
                  * If we were sized then adjust the horizontal splitbar
                  * to be top-aligned.
                  */
                 npdoc->cyHorzSplitPos += (aswp[0].cy - aswp[1].cy);

                 /*
                  * Set or clear the DF_HSPLITOVERFLOW and DF_SPLITHORZ flags.
                  */
                 if (npdoc->cyHorzSplitPos < 0) {
                     npdoc->fs |= DF_HSPLITOVERFLOW;
                     npdoc->fs &= ~DF_SPLITHORZ;
                 } else {
                     npdoc->fs &= ~DF_HSPLITOVERFLOW;
                     npdoc->fs |= DF_SPLITHORZ;
                 }
             }

             if (npdoc->fs & (DF_SPLITVERT | DF_VSPLITOVERFLOW)) {

                 WinQueryWindowRect(hwnd, &rclClient);
                 WinCalcFrameRect(hwnd, &rclClient, TRUE);

                 /*
                  * Set or clear the DF_VSPLITOVERFLOW and DF_SPLITVERT flags.
                  */
                 if (npdoc->cxVertSplitPos >
                         (((SHORT)rclClient.xRight - (SHORT)rclClient.xLeft) -
                         cxVertSplitbar)) {
                     npdoc->fs |= DF_VSPLITOVERFLOW;
                     npdoc->fs &= ~DF_SPLITVERT;
                 } else {
                     npdoc->fs &= ~DF_VSPLITOVERFLOW;
                     npdoc->fs |= DF_SPLITVERT;
                 }
             }
         }

 #undef aswp

         return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
         break;


     case WM_CALCVALIDRECTS:

         /*
          * We do our own WM_CALCVALIDRECTS processing
          * because the frame manager uses the window
          * rectangle of FID_CLIENT, which in our case
          * might be smaller than the 'client area' due
          * to window splitting.
          */

 #define prclOld ((PRECTL)&(((PRECTL)mp1)[0]))
 #define prclNew ((PRECTL)&(((PRECTL)mp1)[1]))

         /*
          * Calculate the client rectangle of hwnd in its
          * parent's coordinates.
          */
         WinQueryWindowRect(hwnd, (PRECTL)prclOld);
         hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         WinMapWindowPoints(hwnd, hwndParent, (PPOINTL)prclOld, 2);
         WinCalcFrameRect(hwnd, (PRECTL)prclOld, TRUE);

         /*
          * Calculate the client rect for the
          * destination of the frame window.
          */
         WinCalcFrameRect(hwnd, (PRECTL)prclNew, TRUE);

         /*
          * Top align destination bits.
          */
         prclNew->yBottom += (prclNew->yTop - prclNew->yBottom) -
                             (prclOld->yTop - prclOld->yBottom);





 #undef prclOld
 #undef prclNew

         prcl = PVOIDFROMMP(mp1);     /* array of src and dest rectangles
         pswp = PVOIDFROMMP(mp2);


         /*
          * Return 0 since we've already aligned the bits.
          */
         return(0);
         break;



     case WM_QUERYFRAMECTLCOUNT:
         cFrameCtls = (int)(ULONG)(*pfnFrameWndProc)(hwnd, WM_QUERYFRAMECTLCOU

         /*
          * Max number of additional frame controls is seven
          * Two splitbars, two additional scrollbars, and three
          * additional client windows.  Throw in 3 more just to
          * be sure we don't trash memory
          */
         return (MRFROMSHORT(cFrameCtls + 7 + 3));


     case WM_FORMATFRAME:
         return (MDIFormatFrame(hwnd, ((PSWP)PVOIDFROMMP(mp1)), mp2));
         break;

     case WM_BUTTON1UP:
         mres =  (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);

         /*
          * If we're minimized we need to do activation ourself
          * and bring up the system menu.
          */
         if (!(WinQueryWindowUShort(hwnd, QWS_FLAGS) & FF_ACTIVE)) {

             /*
              * Only do this if we're minimized.
              */
             if (WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MINIMIZED) {

                 WinSetActiveWindow(HWND_DESKTOP, hwnd);

                 /*
                  * Bring up the system menu if there is one.
                  */
                 WinSendDlgItemMsg(hwnd, FID_SYSMENU, MM_STARTMENUMODE,
                         MPFROMSHORT(TRUE), 0L);
             }
         }
         break;

     case WM_DESTROY:
         /*
          * If this document is maximized, remove the AabSysMenu.
          */

         /* MULTIPLEMENU */

 /* Not needed since we removed the system menu when it lost
         activation ???????????????????? */

    /*
         if (WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED)
             RemoveAabSysMenu();
    */


         hwndClient = WinWindowFromID(hwnd, FID_CLIENT);
         npdoc = NPDOCFROMCLIENT(hwndClient);

         /*
          * Make sure these windows are on the frame so they
          * get destroyed.
          */
         WinSetParent(npdoc->hwndSysMenu, hwnd, FALSE);
         WinSetParent(npdoc->hwndMinmax, hwnd, FALSE);

         WinSendMsg(npdoc->hwndSysMenu, MM_DELETEITEM,
                     MPFROM2SHORT(-127, TRUE),0L);


         fDestroying=TRUE;

         RemoveFromWindowMenu(npdoc);

         if (WinIsWindow(hab, npdoc->hwndMainMenu)) {
             WinDestroyWindow(npdoc->hwndMainMenu);
             }

         WinFreeMem(hHeap, (NPBYTE)npdoc, sizeof(DOC));

         if (npdocFirst==NULL) {
             /* No documents left, so put the first menu back on the main MDI
             window */

             /* need to make sure the hwndFirstMenu still exists, since it
                 is dead by now when the app is being closed down. */

             if (WinIsWindow(hab, hwndFirstMenu)) {
                 WinSetParent(hwndFirstMenu, hwndMDIFrame, FALSE);
                 WinSendMsg(hwndMDIFrame, WM_UPDATEFRAME, 0L, 0L);
                 }
             }

         return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);

     default:
         return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
     }
 }

 MRESULT MDIMinMaxFrame(HWND hwnd, MPARAM mp1, MPARAM mp2)
 {
     PSWP pswp;

     pswp = (PSWP)PVOIDFROMMP(mp1);
     if ((pswp->fs & SWP_MAXIMIZE) &&
             ((WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED) == 0L)) {
         pswp->cy += cyTitlebar;
         AddAabSysMenu(hwnd);
         SetMainTitleText(hwnd);
     } else if (((pswp->fs & SWP_RESTORE) || (pswp->fs & SWP_MINIMIZE)) &&
             (WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED)) {
         RemoveAabSysMenu(hwnd);
         ClearMainTitleText();
     }
     return (*pfnFrameWndProc)(hwnd, WM_MINMAXFRAME, mp1, mp2);
 }

 MRESULT MDIFormatFrame(HWND hwnd, PSWP aswp, MPARAM mp2)
 {
     SWP swpClient;
     PSWP pswpHScroll, pswpVScroll;
     USHORT chwnd, iswpNext;
     register NPDOC npdoc;
     SHORT cyHorzSplitPos, cxVertSplitPos;

     iswpNext = chwnd = (USHORT) (ULONG)(*pfnFrameWndProc)(hwnd, WM_FORMATFRAM
                             aswp, mp2);

     FindSwp(aswp, chwnd, FID_HORZSCROLL, (PSWP FAR *)&pswpHScroll);
     FindSwp(aswp, chwnd, FID_VERTSCROLL, (PSWP FAR *)&pswpVScroll);

     npdoc = NPDOCFROMCLIENT(aswp[chwnd - 1].hwnd);

     cyHorzSplitPos = npdoc->cyHorzSplitPos;
     cxVertSplitPos = npdoc->cxVertSplitPos;

     /*
      * Save the client rectangle away because we
      * want to do thing based on the original
      * client rectangle as well as move the
      * client's ordering in the SWP list behind
      * the other client windows.
      */
     swpClient = aswp[chwnd - 1];

     /*
      * Start from the client window's SWP
      * since we're going to move it to
      * the end.
      */
     iswpNext = (chwnd - 1);

     if (npdoc->fsStyle & DS_VERTSPLITBAR) {

         /*
          * If the horizontal scrollbar is being hidden
          * then we certainly don't need to be around...
          */
         if (pswpHScroll->fs & SWP_HIDE) {

             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSCROLL2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSPLITBAR),
                     &iswpNext);

         } else if (npdoc->fs & DF_VSPLITOVERFLOW) {

             /*
              * First hide CLIENT3 and VERTSCROLL2
              * since we know they're going away.
              */
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSCROLL2),
                     &iswpNext);

             /*
              * Hide CLIENT4 since it won't be needed for now.
              */
             if (npdoc->fsStyle & DS_HORZSPLITBAR) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4),
                         &iswpNext);
             }

             /*
              * If there isn't any room for even the hidden
              * representation then get rid of it...
              */
             if (pswpHScroll->cx < (SHORT) (cxVertSplitbar * 3)) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSPLITBA
                         &iswpNext);
             } else {
                 SetSwpPos(&aswp[iswpNext++],
                         WinWindowFromID(hwnd, ID_VERTSPLITBAR), NULL,
                         pswpHScroll->x + pswpHScroll->cx - cxVertSplitbar,
                         pswpHScroll->y + cyBorder, cxVertSplitbar,
                         pswpHScroll->cy - cyBorder, pswpHScroll->fs);

                 pswpHScroll->cx -= cxVertSplitbar;
             }

         } else if (npdoc->fs & DF_SPLITVERT) {

             /*
              * Format the client windows.
              *
              * If we're split horzintally as well then we
              * need to show the fourth client.
              */

             /*
              * If we're split horizontally and there was
              * enough room to be split then format
              * the 'fourth' client window.
              */
             if ((npdoc->fs & DF_SPLITHORZ) &&
                     (pswpVScroll->cy > (SHORT) (cyHorzSplitbar * 3))) {
                 aswp[iswpNext].hwnd = WinWindowFromID(hwnd, ID_CLIENT4);
                 aswp[iswpNext].hwndInsertBehind = NULL;
                 aswp[iswpNext].x = swpClient.x +
                         (cxVertSplitPos + cxVertSplitbar);
                 aswp[iswpNext].cx = swpClient.cx -
                         (cxVertSplitPos + cxVertSplitbar);
                 aswp[iswpNext].y = swpClient.y;
                 aswp[iswpNext].cy = cyHorzSplitPos;
                 aswp[iswpNext].fs = swpClient.fs | SWP_SHOW;

                 iswpNext++;
             }

             aswp[iswpNext].hwnd = WinWindowFromID(hwnd, ID_CLIENT2);
             aswp[iswpNext].hwndInsertBehind = NULL;
             aswp[iswpNext].x = swpClient.x +
                     (cxVertSplitPos + cxVertSplitbar);
             aswp[iswpNext].cx = swpClient.cx -
                     (cxVertSplitPos + cxVertSplitbar);
             /*
              * If we're split horizontally and there was
              * enough room to be split then format
              * the 'second' client window against the
              * 'fourth' client window.
              */
             if ((npdoc->fs & DF_SPLITHORZ) &&
                     (pswpVScroll->cy > (SHORT) (cyHorzSplitbar * 3))) {
                 aswp[iswpNext].y = swpClient.y +
                         (cyHorzSplitPos + cyHorzSplitbar);
                 aswp[iswpNext].cy = swpClient.cy -
                         (cyHorzSplitPos + cyHorzSplitbar);
             } else {
                 aswp[iswpNext].y = swpClient.y;
                 aswp[iswpNext].cy = swpClient.cy;
             }
             aswp[iswpNext].fs = swpClient.fs | SWP_SHOW;

             iswpNext++;

         } else {
             /*
              * If we're not split then format the window with the
              * splitbar to the left of the horizontal scrollbar.
              */
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSCROLL2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT2),
                     &iswpNext);

             /*
              * Hide ID_CLIENT4 since it won't be needed for now.
              */
             if (npdoc->fsStyle & DS_HORZSPLITBAR) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4),
                         &iswpNext);
             }

             aswp[iswpNext].hwnd = WinWindowFromID(hwnd, ID_VERTSPLITBAR);
             aswp[iswpNext].hwndInsertBehind = NULL;
             aswp[iswpNext].x = pswpHScroll->x;
             aswp[iswpNext].y = pswpHScroll->y + cyBorder;
             aswp[iswpNext].cx = cxVertSplitbar;
             aswp[iswpNext].cy = pswpHScroll->cy - cyBorder;
             aswp[iswpNext].fs = pswpHScroll->fs;
             pswpHScroll->x += cxVertSplitbar;
             pswpHScroll->cx -= cxVertSplitbar;

             iswpNext++;
         }
     }

     if (npdoc->fsStyle & DS_HORZSPLITBAR) {
         /*
          * If the horizontal scrollbar is being hidden
          * then we certainly don't need to be around...
          */
         if (pswpVScroll->fs & SWP_HIDE) {

             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT3),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSCROLL2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSPLITBAR),
                     &iswpNext);

         } else if (npdoc->fs & DF_HSPLITOVERFLOW) {

             /*
              * First hide CLIENT3 and VERTSCROLL2
              * since we know they're going away.
              */
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT3),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSCROLL2),
                     &iswpNext);

             /*
              * Hide CLIENT4 since it won't be needed for now.
              */
             if (npdoc->fsStyle & DS_VERTSPLITBAR) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4),
                         &iswpNext);
             }

             /*
              * If there isn't any room for even the hidden
              * representation then get rid of it
              */
             if (pswpVScroll->cy < (SHORT)(cyHorzSplitbar * 3)) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSPLITBA
                         &iswpNext);
             } else {
                 SetSwpPos(&aswp[iswpNext++],
                         WinWindowFromID(hwnd, ID_HORZSPLITBAR), NULL,
                         pswpVScroll->x, pswpVScroll->y, pswpVScroll->cx - cxB
                         cyHorzSplitbar, pswpVScroll->fs);

                 pswpVScroll->y += cyHorzSplitbar;
                 pswpVScroll->cy -= cyHorzSplitbar;
             }

         } else if (npdoc->fs & DF_SPLITHORZ) {

             if (pswpVScroll->cy > (SHORT)(cyHorzSplitbar * 3)) {
                 /*
                  * Format the client windows.
                  */
                 SetSwpPos(&aswp[iswpNext++],
                         WinWindowFromID(hwnd, ID_CLIENT3), NULL,
                         swpClient.x, swpClient.y,
                         /*
                          * If we're split vertically then format
                          * the client against the vertical splitbar
                          * as well as the horzontal splitbar.
                          */
                         (npdoc->fs & DF_SPLITVERT) ? cxVertSplitPos : swpClie
                         cyHorzSplitPos, swpClient.fs | SWP_SHOW);

             } else {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT3),
                         &iswpNext);

                 /*
                  * Hide CLIENT4 since it won't be needed for now.
                  */
                 if (npdoc->fsStyle & DS_VERTSPLITBAR) {
                     HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4
                             &iswpNext);
                 }
             }

         } else {
             /*
              * If we're not split then format the window with the
              * splitbar at the top of the vertical scrollbar.
              */
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSCROLL2),
                     &iswpNext);

             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT3),
                     &iswpNext);

             if (npdoc->fsStyle & DS_VERTSPLITBAR) {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4),
                     &iswpNext);
             }

             if (pswpVScroll->cy > (SHORT)(cyHorzSplitbar * 3)) {
                 SetSwpPos(&aswp[iswpNext++],
                         WinWindowFromID(hwnd, ID_HORZSPLITBAR), NULL,
                         pswpVScroll->x, pswpVScroll->y + pswpVScroll->cy -
                             cyHorzSplitbar,
                         pswpVScroll->cx - cxBorder, cyHorzSplitbar, pswpVScro

                 pswpVScroll->cy -= cyHorzSplitbar;
             } else {
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSPLITBA
                         &iswpNext);
             }
         }
     }

     /*
      * Now format the original client window.
      */
     aswp[iswpNext] = swpClient;

     if (npdoc->fs & (DF_SPLITHORZ | DF_SPLITVERT)) {

         /*
          * Adjust the main client window for the splitbars.
          */
         if (npdoc->fs & DF_SPLITVERT) {
             aswp[iswpNext].cx = cxVertSplitPos;
         }

         if ((npdoc->fs & DF_SPLITHORZ) &&
                 (pswpVScroll->cy > (SHORT)(cyHorzSplitbar * 3))) {
             aswp[iswpNext].y += (cyHorzSplitPos + cyHorzSplitbar);
             aswp[iswpNext].cy -= (cyHorzSplitPos + cyHorzSplitbar);
         }
     }

     iswpNext++;

     if (npdoc->fs & DF_SPLITHORZ) {
         /*
          * Format the scrollbars and the splitbar.
          */
         if (pswpVScroll->cy > (SHORT)(cyHorzSplitbar * 3)) {
             SetSwpPos(&aswp[iswpNext++],
                     WinWindowFromID(hwnd, ID_HORZSPLITBAR), NULL,
                     swpClient.x, swpClient.y + cyHorzSplitPos,
                     swpClient.cx + pswpVScroll->cx - cxBorder, cyHorzSplitbar
                     pswpVScroll->fs | SWP_SHOW);

             SetSwpPos(&aswp[iswpNext++],
                     WinWindowFromID(hwnd, ID_VERTSCROLL2), NULL,
                     pswpVScroll->x, pswpVScroll->y,
                     pswpVScroll->cx, cyHorzSplitPos + (cyBorder * 2),
                     pswpVScroll->fs | SWP_SHOW);

             pswpVScroll->y += (cyHorzSplitPos + cyHorzSplitbar);
             pswpVScroll->cy -= (cyHorzSplitPos + cyHorzSplitbar);

         } else {
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_VERTSCROLL2),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_HORZSPLITBAR),
                     &iswpNext);
             HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT3),
                     &iswpNext);
             if (npdoc->fsStyle & DS_VERTSPLITBAR)
                 HideSwp(&aswp[iswpNext], WinWindowFromID(hwnd, ID_CLIENT4),
                         &iswpNext);
         }
     }

     if (npdoc->fs & DF_SPLITVERT) {

         SetSwpPos(&aswp[iswpNext++],
                 WinWindowFromID(hwnd, ID_VERTSPLITBAR), NULL,
                 swpClient.x + cxVertSplitPos, pswpHScroll->y + cyBorder,
                 cxVertSplitbar, swpClient.cy + pswpHScroll->cy - cyBorder,
                 pswpHScroll->fs);

         SetSwpPos(&aswp[iswpNext++],
                 WinWindowFromID(hwnd, ID_HORZSCROLL2), NULL,
                 swpClient.x + cxVertSplitPos + cxVertSplitbar - cyBorder,
                 pswpHScroll->y, pswpHScroll->cx - (cxVertSplitPos + cxVertSpl
                 pswpHScroll->cy, pswpVScroll->fs | SWP_SHOW);

         pswpHScroll->cx = cxVertSplitPos + (cxBorder * 2);
     }

     return ( (MRESULT) iswpNext);
 }


 MDIINIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\MDIINIT.C

 /***************************************************************************\
 * mdiinit.c - MDI initialization funtions.
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WININPUT
 #define INCL_WINMENUS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINHEAP
 #define INCL_GPIBITMAPS
 #define INCL_GPILCIDS
 #define INCL_DEV

 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"

 /* Function prototypes */
 VOID InitMDIPointers(VOID);
 BOOL SetupMenuStuff(HPS hps);
 VOID InitSplitbars(VOID);
 VOID InitInitialDocPlacement(VOID);


 BOOL MDIInit(VOID)
 {
     HPS hps;

     /*
      * Register the splitbar window class.
      */
     if (!WinRegisterClass(NULL, szSplitbarClass, (PFNWP)SplitbarWndProc,
             CS_MOVENOTIFY | CS_SYNCPAINT, 0))
         return(FALSE);

     InitMDIPointers();

     pfnMainFrameWndProc = WinSubclassWindow(hwndMDIFrame,
             (PFNWP)MainFrameWndProc);

     hps = WinGetPS(hwndMDI);

     if (SetupMenuStuff(hps) == FALSE)
         return(FALSE);

     WinReleasePS(hps);

     InitSplitbars();

     InitInitialDocPlacement();

     return(TRUE);
 }


 VOID InitInitialDocPlacement(VOID)
 {
     RECTL rclDesktop;

     /*
      * New documents will be 2/3 the width of the
      * screen and 1/2 the height of the screen.
      */
     cxNewDoc = (cxScreen * 2) / 3;
     cyNewDoc = cyScreen / 2;

     WinQueryWindowRect(hwndMDI, &rclDesktop);
     xNextNewDoc = 0;
     yNextNewDoc = (SHORT)rclDesktop.yTop - cyNewDoc;

     xCascadeInc = cxSizeBorder + (cxMinmaxButton / 2);
     yCascadeInc = cySizeBorder + cyTitlebar - cyBorder;

     xCascadeSlot = 0;
 }


 VOID InitSplitbars()
 {
     /*
      * Calculate the values cxVertSplitbar and cyHorzSplitbar.
      *
      * The horzontal splitbar is 1/3 the height of the vertical
      * scrollbar arrow.  We use the aspect ratio of the display
      * to determine the width of the vertical splitbar.
      */
     cyHorzSplitbar = cyVScrollArrow / 3;
     cxVertSplitbar = (USHORT)(cyHorzSplitbar *
             rglDevCaps[CAPS_HORIZONTAL_RESOLUTION] /
             rglDevCaps[CAPS_VERTICAL_RESOLUTION]);
 }


 VOID InitMDIPointers(VOID)
 {
     hptrHorzSplit = WinLoadPointer(HWND_DESKTOP, NULL, IDP_HSPLIT);
     hptrVertSplit = WinLoadPointer(HWND_DESKTOP, NULL, IDP_VSPLIT);
     hptrHVSplit = WinLoadPointer(HWND_DESKTOP, NULL, IDP_HVSPLIT);
     hptrArrow = WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, TRUE);
 }


 BOOL SetupMenuStuff(HPS hps)
 {
     BITMAPINFOHEADER bmpNew, bmpCurrent;
     SIZEL gsize;
     RECTL rclSrc;
     POINTL ptlDst;
     DRIVDATA driv;
     DEVOPENSTRUC dop;

     HWND hwndMenu;   /* This is the hwnd of the initial menu bar */

     hbmChildSysMenu = WinGetSysBitmap(HWND_DESKTOP, SBMP_CHILDSYSMENU);

     GpiQueryFontMetrics(hps, (ULONG)sizeof(FONTMETRICS),
             (PFONTMETRICS)&fmSystemFont);

     GpiQueryBitmapParameters(hbmChildSysMenu, &bmpCurrent);
     bmpNew.cbFix = sizeof(BITMAPINFOHEADER);
     bmpNew.cx = bmpCurrent.cx + (((bmpCurrent.cx - cxBorder) / 4) * 2);
     bmpNew.cy = bmpCurrent.cy;
     bmpNew.cPlanes = bmpCurrent.cPlanes;
     bmpNew.cBitCount = bmpCurrent.cBitCount;
     hbmAabChildSysMenu = GpiCreateBitmap(hps, &bmpNew, 0L, NULL, NULL);

     driv.cb = sizeof(ULONG) * 11;
     driv.lVersion = 0;
     driv.szDeviceName[0] = 0;
     driv.abGeneralData[0] = 0L;

     dop.pszDriverName = NULL;
     dop.pdriv = (PDRIVDATA)&driv;

     dop.pszLogAddress =
     dop.pszDataType =
     dop.pszComment =
     dop.pszQueueProcName =
     dop.pszQueueProcParams =
     dop.pszSpoolerParams =
     dop.pszNetworkParams = NULL;

     gsize.cx = bmpNew.cx;
     gsize.cy = bmpNew.cy;

     hdcMem = DevOpenDC(hps, OD_MEMORY, "*", 8L, (PDEVOPENDATA)&dop, NULL);
     hpsMem = GpiCreatePS(NULL, hdcMem, (PSIZEL)&gsize,
             PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC);
     GpiSetBitmap(hpsMem, hbmAabChildSysMenu);

     rclSrc.xLeft = 0;
     rclSrc.yBottom = 0;
     rclSrc.xRight = bmpNew.cx;
     rclSrc.yTop = bmpNew.cy;
     WinFillRect(hpsMem, (PRECTL)&rclSrc, CLR_FALSE);

     rclSrc.xRight = bmpCurrent.cx - cxBorder;
     rclSrc.yTop = bmpCurrent.cy;
     ptlDst.x = (bmpCurrent.cx - cxBorder) / 4;
     ptlDst.y = 0;
     WinDrawBitmap(hpsMem, hbmChildSysMenu, (PRECTL)&rclSrc, (PPOINTL)&ptlDst,
             CLR_TRUE, CLR_FALSE, DBM_NORMAL);

     GpiSetBitmap(hpsMem, NULL);
     GpiDestroyPS(hpsMem);
     DevCloseDC(hdcMem);

     /*
      * Get menu window handles.
      */

     hwndMenu = WinWindowFromID(hwndMDIFrame, FID_MENU);

 /*
     ** Must not remember the hwnd of the submenu for the list of doc window's
     ** since it will change as the menu bar changes


     WinSendMsg(hwndMenu, MM_QUERYITEM, MPFROM2SHORT(IDM_WINDOW, FALSE),
                MPFROMP(&mi));
     hwndWindowMenu = mi.hwndSubMenu;
 */

     hwndSysMenu = WinWindowFromID(hwndMDIFrame, FID_SYSMENU);

 /*
     hwndAppMenu = WinWindowFromID(hwndMDIFrame, FID_MENU);
 */

     /* set up menuitem for application menu bar sysmenu */
     WinSendMsg(hwndMenu, MM_QUERYITEM, MPFROM2SHORT(IDM_AABDOCSYSMENU, FALSE)
                MPFROMP(&miAabSysMenu));
     miAabSysMenu.hItem = (ULONG)hbmAabChildSysMenu;
     WinSendMsg(hwndMenu, MM_REMOVEITEM, MPFROM2SHORT(IDM_AABDOCSYSMENU, FALSE
                (MPARAM)NULL);

     return(TRUE);
 }


 VOID MDITerminate(VOID)
 {
 BOOL fAabSysMenu;
 NPDOC npdoc;

     fAabSysMenu=FALSE;
     npdoc=npdocFirst;

     while ((npdoc!=NULL) && (!fAabSysMenu)) {
         fAabSysMenu=npdoc->fAabSysMenu;
         npdoc=npdoc->npdocNext;
         }

     /*
      * Only destroy the AabSysMenu if it isn't already in *any* AAB.
      * If it is we don't need to destroy it since the frame window
      * manager will.
      */

   /*  if (fAabSysMenu == FALSE) ????????????????????????  */

     if (WinIsWindow(hab,miAabSysMenu.hwndSubMenu)) {
         WinDestroyWindow(miAabSysMenu.hwndSubMenu);
         }

     GpiDeleteBitmap(hbmChildSysMenu);
     GpiDeleteBitmap(hbmAabChildSysMenu);

     WinDestroyPointer(hptrHorzSplit);
     WinDestroyPointer(hptrVertSplit);
     WinDestroyPointer(hptrHVSplit);
     WinDestroyPointer(hptrArrow);
 }


 MDISPLIT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MDI\MDISPLIT.C

 /***************************************************************************\
 * split.c - Code for window splitting
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_WINSYS
 #define INCL_WINCOMMON
 #define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WINMENUS
 #define INCL_WININPUT
 #define INCL_WINHEAP
 #define INCL_WINSCROLLBARS
 #define INCL_WINFRAMEMGR
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_GPIBITMAPS
 #define INCL_GPIPRIMITIVES

 #include <os2.h>
 #include "app.h"
 #include "appdata.h"
 #include "mdi.h"
 #include "mdidata.h"

 VOID InvertRect(HPS, PRECTL);
 VOID TrackSplitbars(HWND, USHORT, SHORT, SHORT);
 VOID FillSplitbarInteriors(USHORT, HWND, HWND);
 HWND QueryBotLeftClient(HWND hwndFrame, NPDOC npdoc);
 VOID DrawTrackRects(HPS, PRECTL, SHORT, SHORT, USHORT);
 VOID MoveTrackRects(HPS, PRECTL, USHORT, SHORT, SHORT, SHORT, SHORT);
 VOID CalcTrackRect(PRECTL, PRECTL, SHORT, USHORT);
 VOID SetSplitbarPos(HWND, PRECTL, SHORT, SHORT, USHORT);
 HWND QueryBotLeftClient(HWND hwndFrame, NPDOC npdoc);

 MRESULT EXPENTRY SplitbarWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
 {
     HPS hps;
     POINTL ptl;
     RECTL rclPaint;
     HWND hwndClient, hwndFrame, hwndOrigin;
     BOOL fControl;
     register NPDOC npdoc;

     switch (msg) {

     case WM_PAINT:
         hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         hwndClient = WinWindowFromID(hwndFrame, FID_CLIENT);

         npdoc = NPDOCFROMCLIENT(hwndClient);

         hps = WinBeginPaint(hwnd, NULL, NULL);

         WinQueryWindowRect(hwnd, &rclPaint);

         if (WinQueryWindowUShort(hwnd, QWS_ID) == ID_VERTSPLITBAR) {
             if (npdoc->fs & DF_SPLITVERT) {
                 WinDrawBorder(hps, &rclPaint, 1, 0, SYSCLR_WINDOWFRAME,
                             SYSCLR_WINDOW, DB_STANDARD);
                 /*
                  * Make the interiors of the splitbars
                  * visually correct.
                  */
                 FillSplitbarInteriors(npdoc->fs, hwnd,
                         WinWindowFromID(hwndFrame, ID_HORZSPLITBAR));
             } else {
                 WinFillRect(hps, &rclPaint, SYSCLR_WINDOWFRAME);
             }
         } else {
             if (npdoc->fs & DF_SPLITHORZ) {
                 WinDrawBorder(hps, &rclPaint, 0, 1, SYSCLR_WINDOWFRAME,
                         SYSCLR_WINDOW, DB_STANDARD | DB_INTERIOR);
             } else {
                 WinFillRect(hps, &rclPaint, SYSCLR_WINDOWFRAME);
             }
         }

         WinEndPaint(hps);
         break;

     case WM_MOUSEMOVE:
         hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         hwndClient = WinWindowFromID(hwndFrame, FID_CLIENT);

         npdoc = NPDOCFROMCLIENT(hwndClient);

         ptl.x = SHORT1FROMMP(mp1);
         ptl.y = SHORT2FROMMP(mp1);

         hwndOrigin = QueryBotLeftClient(hwndFrame, npdoc);
         WinMapWindowPoints(hwnd, hwndOrigin, (PPOINTL)&ptl, 1);

         if (WinQueryWindowUShort(hwnd, QWS_ID) == ID_VERTSPLITBAR) {

             if ((npdoc->fs & DF_SPLITHORZ) &&
                     ((SHORT)ptl.y > (npdoc->cyHorzSplitPos - (cyHorzSplitbar)
                     ((SHORT)ptl.y <= (npdoc->cyHorzSplitPos + (cyHorzSplitbar
                 WinSetPointer(HWND_DESKTOP, hptrHVSplit);
             } else {
                 WinSetPointer(HWND_DESKTOP, hptrVertSplit);
             }
         } else {
             if ((npdoc->fs & DF_SPLITVERT) &&
                     ((SHORT)ptl.x > (npdoc->cxVertSplitPos - (cxVertSplitbar)
                     ((SHORT)ptl.x <= (npdoc->cxVertSplitPos + (cxVertSplitbar
                 WinSetPointer(HWND_DESKTOP, hptrHVSplit);
             } else {
                 WinSetPointer(HWND_DESKTOP, hptrHorzSplit);
             }
         }
         break;

     case WM_BUTTON1DOWN:
     case WM_BUTTON1DBLCLK:
         /*
          * Get the control key state here so the user doesn't
          * have to hold it down the whole time to get the
          * desired effect.
          */
         fControl = (WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000);

         hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         hwndClient = WinWindowFromID(hwndFrame, FID_CLIENT);

         /*
          * Update the entire window before we start
          * tracking with the splitter bars.
          */
         WinUpdateWindow(hwndFrame);

         npdoc = NPDOCFROMCLIENT(hwndClient);

         ptl.x = SHORT1FROMMP(mp1);
         ptl.y = SHORT2FROMMP(mp1);

         hwndOrigin = QueryBotLeftClient(hwndFrame, npdoc);
         WinMapWindowPoints(hwnd, hwndOrigin, (PPOINTL)&ptl, 1);

         if (WinQueryWindowUShort(hwnd, QWS_ID) == ID_VERTSPLITBAR) {
             if ((npdoc->fs & DF_SPLITHORZ) &&
                     ((SHORT)ptl.y > (npdoc->cyHorzSplitPos - (cyHorzSplitbar)
                     ((SHORT)ptl.y <= (npdoc->cyHorzSplitPos + (cyHorzSplitbar
                 TrackSplitbars(hwndClient, SPS_VERT | SPS_HORZ, (SHORT)ptl.x,
                         (SHORT)ptl.y);
             } else {
                 TrackSplitbars(hwndClient, SPS_VERT, (SHORT)ptl.x,
                         (SHORT)ptl.y);
             }
         } else {
             if ((npdoc->fs & DF_SPLITVERT) &&
                     ((SHORT)ptl.x > (npdoc->cxVertSplitPos - (cxVertSplitbar)
                     ((SHORT)ptl.x <= (npdoc->cxVertSplitPos + (cxVertSplitbar
                 TrackSplitbars(hwndClient, SPS_VERT | SPS_HORZ, (SHORT)ptl.x,
                         (SHORT)ptl.y);
             } else {
                 TrackSplitbars(hwndClient, SPS_HORZ, (SHORT)ptl.x,
                         (SHORT)ptl.y);
             }
         }

         /*
          * If the control key is down, we leave
          * the focus where it is.
          */
         if (fControl == FALSE)
             WinSetFocus(HWND_DESKTOP, hwndClient);

         break;

     case WM_MOVE:
     case WM_SIZE:
         WinInvalidateRect(hwnd, NULL, FALSE);
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }

     return (0L);
 }


 VOID FillSplitbarInteriors(USHORT fs, HWND hwndVert, HWND hwndHorz)
 {
     HPS hps;
     RECTL rclVertSplitbar, rclHorzSplitbar;

     if (fs & DF_SPLITVERT) {
         hps = WinGetPS(hwndVert);
         WinQueryWindowRect(hwndVert, &rclVertSplitbar);
         WinInflateRect(NULL, &rclVertSplitbar, -cxBorder, 0);
         WinFillRect(hps, &rclVertSplitbar, SYSCLR_WINDOW);
         WinReleasePS(hps);
     }

     if (fs & DF_SPLITHORZ) {
         hps = WinGetPS(hwndHorz);
         WinQueryWindowRect(hwndHorz, &rclHorzSplitbar);
         WinInflateRect(NULL, &rclHorzSplitbar, 0, -cyBorder);
         WinFillRect(hps, &rclHorzSplitbar, SYSCLR_WINDOW);
         WinReleasePS(hps);
     }
 }


 BOOL CreateSplitbarWindows(HWND hwndFrame, NPDOC npdoc)
 {
     USHORT fsStyle;
     register NPVIEW npview, npviewNew;
     HWND hwndNewClient;

     fsStyle = npdoc->fsStyle;
     npview = npdoc->npviewFirst;

     if (fsStyle & DS_VERTSPLITBAR) {
         if (WinCreateWindow(hwndFrame, WC_SCROLLBAR, "",
                 SBS_HORZ, 0, 0, 0, 0, hwndFrame,
                 WinWindowFromID(hwndFrame, FID_HORZSCROLL),
                 ID_HORZSCROLL2, NULL, NULL) == NULL)
             return (FALSE);

         if (WinCreateWindow(hwndFrame, szSplitbarClass, "",
                 (ULONG)SPS_VERT, 0, 0, 0, 0, hwndFrame,
                 WinWindowFromID(hwndFrame, FID_MINMAX),
                 ID_VERTSPLITBAR, NULL, NULL) == NULL)
             return (FALSE);

         hwndNewClient = WinCreateWindow(hwndFrame, szDocClass, "",
                 0L, 0, 0, 0, 0, hwndFrame,
                 WinWindowFromID(hwndFrame, FID_CLIENT),
                 ID_CLIENT2, NULL, NULL);
         if (hwndNewClient == NULL)
             return (FALSE);

         /*
          * Link ID_CLIENT2 into npdoc's view list.
          */
         npviewNew = NPVIEWFROMCLIENT(hwndNewClient);
         npview->npviewNext = npviewNew;
         npview = npviewNew;
     }

     if (fsStyle & DS_HORZSPLITBAR) {
         if (WinCreateWindow(hwndFrame, WC_SCROLLBAR, "",
                 SBS_VERT, 0, 0, 0, 0, hwndFrame,
                 WinWindowFromID(hwndFrame, FID_VERTSCROLL),
                 ID_VERTSCROLL2, NULL, NULL) == NULL)
             return (FALSE);

         if (WinCreateWindow(hwndFrame, szSplitbarClass, "",
                 (ULONG)SPS_HORZ, 0, 0, 0, 0, hwndFrame,
                 WinWindowFromID(hwndFrame, FID_MINMAX),
                 ID_HORZSPLITBAR, NULL, NULL) == NULL)
             return (FALSE);

         /*
          * If we're split vertically as well, then we want to
          * insert ID_CLIENT3 behind ID_CLIENT2 and create
          * ID_CLIENT4 behind CLIENT3, otherwise we just create
          * ID_CLIENT3 behind FID_CLIENT.
          */
         if (fsStyle & DS_VERTSPLITBAR) {
             hwndNewClient = WinCreateWindow(hwndFrame, szDocClass, "",
                     0L, 0, 0, 0, 0, hwndFrame,
                     WinWindowFromID(hwndFrame, ID_CLIENT2),
                     ID_CLIENT3, NULL, NULL);
             if (hwndNewClient == NULL)
                 return (FALSE);

             /*
              * Link ID_CLIENT3 into npdoc's view list.
              */
             npviewNew = NPVIEWFROMCLIENT(hwndNewClient);
             npview->npviewNext = npviewNew;
             npview = npviewNew;

             hwndNewClient = WinCreateWindow(hwndFrame, szDocClass, "",
                     0L, 0, 0, 0, 0, hwndFrame,
                     WinWindowFromID(hwndFrame, ID_CLIENT3),
                     ID_CLIENT4, NULL, NULL);
             if (hwndNewClient == NULL)
                 return (FALSE);

             /*
              * Link ID_CLIENT4 into npdoc's view list.
              */
             npviewNew = NPVIEWFROMCLIENT(hwndNewClient);
             npview->npviewNext = npviewNew;
             npview = npviewNew;

         } else {
             hwndNewClient = WinCreateWindow(hwndFrame, szDocClass, "",
                     0L, 0, 0, 0, 0, hwndFrame,
                     WinWindowFromID(hwndFrame, FID_CLIENT),
                     ID_CLIENT3, NULL, NULL);
             if (hwndNewClient == NULL)
                 return (FALSE);

             /*
              * Link ID_CLIENT3 into npdoc's view list.
              */
             npviewNew = NPVIEWFROMCLIENT(hwndNewClient);
             npview->npviewNext = npviewNew;
             npview = npviewNew;
         }
     }

     return (TRUE);
 }


 HWND QueryBotLeftClient(HWND hwndFrame, NPDOC npdoc)
 {
     if (npdoc->fs & DF_SPLITHORZ) {
         /*
          * If there isn't enough room to split
          * then only the main client will be
          * visible so we'll use it.
          */
         if (WinIsWindowVisible(WinWindowFromID(hwndFrame, ID_HORZSPLITBAR)))
             return (WinWindowFromID(hwndFrame, ID_CLIENT3));
         else
             return (WinWindowFromID(hwndFrame, FID_CLIENT));

     } else {
         return (WinWindowFromID(hwndFrame, FID_CLIENT));
     }
 }



 VOID TrackSplitbars(HWND hwndClient, register USHORT fsTrack, SHORT xMouse,
         SHORT yMouse)
 {
     SHORT x, y;
     SHORT xNew, yNew;
     HPS hps;
     RECTL rclClient, rclClientScreen;
     QMSG qmsg;
     AREABUNDLE abnd;
     HWND hwndFrame, hwndOrigin;
     register NPDOC npdoc;
     POINTL ptlPointer;

     hwndFrame = WinQueryWindow(hwndClient, QW_PARENT, FALSE);

     WinLockWindowUpdate(HWND_DESKTOP, hwndFrame);

     /*
      * If we're split horizontally then we want the
      * origin to be a ID_CLIENT3's origin, otherwise
      * we want FID_CLIENT's origin.
      */
     npdoc = NPDOCFROMCLIENT(hwndClient);
     hwndOrigin = QueryBotLeftClient(hwndFrame, npdoc);

     /*
      * We use WinCalcFrameRect() to get the client rectangle
      * rather than WinQueryWindowRect() because the window
      * might already be split, so hwndClient's rectangle wouldn't
      * necessarily include the area we want.
      */
     WinQueryWindowRect(hwndFrame, &rclClient);

     /*
      * Map rectangle to screen coordinates because
      * WinCalcFrameRect() will byte-align the client.
      */
     WinMapWindowPoints(hwndFrame, HWND_DESKTOP, (PPOINTL)&rclClient, 2);
     WinCalcFrameRect(hwndFrame, &rclClient, TRUE);

     WinCopyRect(NULL, &rclClientScreen, &rclClient);
     WinMapWindowPoints(HWND_DESKTOP, hwndOrigin, (PPOINTL)&rclClient, 2);

     WinSetCapture(HWND_DESKTOP, hwndOrigin);

     /*
      * We get a PSF_PARENTCLIP PS so we can draw the
      * splitbars through the other client windows,
      * but still get the PS origin we want.
      */
     hps = WinGetClipPS(hwndOrigin, NULL,
             PSF_PARENTCLIP | PSF_LOCKWINDOWUPDATE);

     abnd.usSymbol = PATSYM_HALFTONE;
     abnd.lColor = CLR_TRUE;
     abnd.lBackColor = CLR_FALSE;
     GpiSetAttrs(hps, PRIM_AREA, ABB_SYMBOL | ABB_COLOR | ABB_BACK_COLOR, 0L,
             (PBUNDLE)&abnd);

     if (fsTrack & SPS_HORZ) {
         if (npdoc->fs & DF_SPLITHORZ) {
             y = npdoc->cyHorzSplitPos + (cyHorzSplitbar / 2);
         } else if (npdoc->fs & DF_HSPLITOVERFLOW) {
             y = 0;
             /*
              * Clear the overflow flag since the
              * user can't track to an overflown
              * position.
              */
             npdoc->fs &= ~DF_HSPLITOVERFLOW;
         } else {
             y = (SHORT)rclClient.yTop - (cyHorzSplitbar / 2);
         }
     }

     if (fsTrack & SPS_VERT) {
         if (npdoc->fs & DF_SPLITVERT) {
             x = npdoc->cxVertSplitPos + (cxVertSplitbar / 2);
         } else if (npdoc->fs & DF_VSPLITOVERFLOW) {
             x = (SHORT)rclClient.xRight - cxVertSplitbar;
             /*
              * Clear the overflow flag since the
              * user can't track to an overflown
              * position.
              */
             npdoc->fs &= ~DF_VSPLITOVERFLOW;
         } else {
             x = (cxVertSplitbar / 2);
         }
     }

     /*
      * If xMouse is -1, then we want to set the pointer
      * position to the position of the splitbars.
      */
     if (xMouse == -1) {
         xMouse = x;
         yMouse = y;
         ptlPointer.x = x;
         ptlPointer.y = y;
         WinMapWindowPoints(hwndOrigin, HWND_DESKTOP, (PPOINTL)&ptlPointer, 1)
         WinSetPointerPos(HWND_DESKTOP, (SHORT)ptlPointer.x, (SHORT)ptlPointer
     }

     DrawTrackRects(hps, &rclClient, x, y, fsTrack);

     while (WinGetMsg(NULL, (PQMSG)&qmsg, NULL, NULL, NULL)) {
         switch (qmsg.msg) {

         case WM_BUTTON1UP:
             DrawTrackRects(hps, &rclClient, x, y, fsTrack);
             WinLockWindowUpdate(HWND_DESKTOP, NULL);
             SetSplitbarPos(hwndClient, &rclClient, x, y, fsTrack);
             goto exit_no_unlock;

         case WM_CHAR:
             if ((((USHORT) (ULONG) qmsg.mp1 &
                     (KC_KEYUP | KC_DEADKEY | KC_COMPOSITE | KC_INVALIDCOMP))
                     == 0) && ((USHORT) (ULONG) qmsg.mp1 & KC_VIRTUALKEY)) {

                 WinQueryPointerPos(HWND_DESKTOP, (PPOINTL)&ptlPointer);
                 switch (SHORT2FROMMP(qmsg.mp2)) {

                 case VK_UP:
                     ptlPointer.y += (cyHorzSplitbar * 2);
                     break;

                 case VK_DOWN:
                     ptlPointer.y -= (cyHorzSplitbar * 2);
                     break;

                 case VK_RIGHT:
                     ptlPointer.x += (cxVertSplitbar * 2);
                     break;

                 case VK_LEFT:
                     ptlPointer.x -= (cxVertSplitbar * 2);
                     break;

                 case VK_ENTER:
                 case VK_NEWLINE:
                     DrawTrackRects(hps, &rclClient, x, y, SPS_HORZ | SPS_VERT
                     WinLockWindowUpdate(HWND_DESKTOP, NULL);
                     SetSplitbarPos(hwndClient, &rclClient, x, y,
                             SPS_HORZ | SPS_VERT);
                     goto exit_no_unlock;

                 case VK_ESC:
                     DrawTrackRects(hps, &rclClient, x, y, SPS_HORZ | SPS_VERT
                     goto exit;
                 }
                 if ((SHORT)ptlPointer.x < (SHORT)rclClientScreen.xLeft)
                     ptlPointer.x = rclClientScreen.xLeft;
                 else if ((SHORT)ptlPointer.x > (SHORT)rclClientScreen.xRight)
                     ptlPointer.x = rclClientScreen.xRight;

                 if ((SHORT)ptlPointer.y < (SHORT)rclClientScreen.yBottom)
                     ptlPointer.y = rclClientScreen.yBottom;
                 else if ((SHORT)ptlPointer.y > (SHORT)rclClientScreen.yTop)
                     ptlPointer.y = rclClientScreen.yTop;

                 WinSetPointerPos(HWND_DESKTOP, (SHORT)ptlPointer.x,
                         (SHORT)ptlPointer.y);
             }
             break;

         case WM_MOUSEMOVE:
             xNew = x + (SHORT1FROMMP(qmsg.mp1) - xMouse);
             xMouse = SHORT1FROMMP(qmsg.mp1);
             yNew = y + (SHORT2FROMMP(qmsg.mp1) - yMouse);
             yMouse = SHORT2FROMMP(qmsg.mp1);

             MoveTrackRects(hps, &rclClient, fsTrack, x, y, xNew, yNew);
             x = xNew;
             y = yNew;
             break;

         default:
             WinDispatchMsg(NULL, (PQMSG)&qmsg);
             break;
         }
     }

 exit:
     WinLockWindowUpdate(HWND_DESKTOP, NULL);
 exit_no_unlock:
     WinReleasePS(hps);
     WinSetCapture(HWND_DESKTOP, NULL);
 }


 VOID SetSplitbarPos(HWND hwndClient, PRECTL prclClient, SHORT x, SHORT y,
         USHORT fsTrack)
 {
     register NPDOC npdoc;
     register NPVIEW npview1, npview2, npview3, npview4;
     HWND hwndFrame;
     RECTL rclTrack;

     npview1 = (NPVIEW)WinQueryWindowUShort(hwndClient, QWS_USER);
     npdoc = npview1->npdoc;
     hwndFrame = WinQueryWindow(hwndClient, QW_PARENT, FALSE);

     if (npdoc->fsStyle & DS_VERTSPLITBAR) {
         npview2 = (NPVIEW)WinQueryWindowUShort(
                 WinWindowFromID(hwndFrame, ID_CLIENT2), QWS_USER);
     }

     if (npdoc->fsStyle & DS_HORZSPLITBAR) {
         npview3 = (NPVIEW)WinQueryWindowUShort(
                 WinWindowFromID(hwndFrame, ID_CLIENT3), QWS_USER);
     }

     if (npdoc->fsStyle & (DS_HORZSPLITBAR | DS_VERTSPLITBAR)) {
         npview4 = (NPVIEW)WinQueryWindowUShort(
                 WinWindowFromID(hwndFrame, ID_CLIENT4), QWS_USER);
     }

     if (fsTrack & SPS_VERT) {
         CalcTrackRect(prclClient, &rclTrack, x, SPS_VERT);
         npdoc->cxVertSplitPos = (SHORT) rclTrack.xLeft;
         if ((npdoc->cxVertSplitPos < (SHORT) cxVertSplitbar) ||
                 (npdoc->cxVertSplitPos >
                 ((SHORT)prclClient->xRight - (cxVertSplitbar * 2)))) {
             npdoc->fs &= ~DF_SPLITVERT;
         } else {
             npdoc->fs |= DF_SPLITVERT;

             /*
              * Setup ID_CLIENT2 with the same yOrigin
              * as FID_CLIENT.
              */
             npview2->yOrigin = npview1->yOrigin;

             /*
              * If we were horizontally split already, then
              * set ID_CLIENT4 to the same yOrigin
              * as ID_CLIENT3.
              */
             if (npdoc->fs & DF_SPLITHORZ)
                 npview4->yOrigin = npview3->yOrigin;
         }
     }

     if (fsTrack & SPS_HORZ) {
         CalcTrackRect(prclClient, &rclTrack, y, SPS_HORZ);
         npdoc->cyHorzSplitPos = (SHORT)rclTrack.yBottom;
         if ((npdoc->cyHorzSplitPos >
                 ((SHORT)prclClient->yTop - (cyHorzSplitbar * 2))) ||
                 (npdoc->cyHorzSplitPos < (SHORT) cyHorzSplitbar)) {
             npdoc->fs &= ~DF_SPLITHORZ;
         } else {
             npdoc->fs |= DF_SPLITHORZ;

             /*
              * Setup ID_CLIENT3 with the same xOrigin
              * as FID_CLIENT.
              */
             npview3->xOrigin = npview1->xOrigin;

             /*
              * If we were vertically split already, then
              * set ID_CLIENT4 to the same xOrigin
              * as ID_CLIENT2.
              */
             if (npdoc->fs & DF_SPLITHORZ)
                 npview4->xOrigin = npview2->xOrigin;
         }
     }

     WinSendMsg(hwndFrame, WM_UPDATEFRAME, 0L, 0L);
 }


 VOID CalcTrackRect(PRECTL prclClient, PRECTL prcl, SHORT coord, USHORT fsTrac
 {
     if (fsTrack & SPS_VERT) {
         WinSetRect(NULL, prcl, coord - (cxVertSplitbar / 2), 0,
                 coord + (cxVertSplitbar - (cxVertSplitbar / 2)),
                 (SHORT)prclClient->yTop);

         if ((SHORT)prcl->xLeft < 0)
             WinOffsetRect(NULL, prcl, (SHORT)-(prcl->xLeft), 0);
         else if ((SHORT)prcl->xRight > (SHORT)prclClient->xRight)
             WinOffsetRect(NULL, prcl,
                     -((SHORT)prcl->xRight - (SHORT)prclClient->xRight), 0);
     } else if (fsTrack & SPS_HORZ) {
         WinSetRect(NULL, prcl, 0, coord - (cyHorzSplitbar / 2),
                 (SHORT)prclClient->xRight,
                 coord + (cyHorzSplitbar - (cyHorzSplitbar / 2)));

         if ((SHORT)prcl->yBottom < 0)
             WinOffsetRect(NULL, prcl, 0, (SHORT)-(prcl->yBottom));
         else if ((SHORT)prcl->yTop > (SHORT)prclClient->yTop)
             WinOffsetRect(NULL, prcl,
                     0, -((SHORT)prcl->yTop - (SHORT)prclClient->yTop));
     }
 }


 VOID DrawTrackRects(HPS hps, PRECTL prclClient, SHORT x, SHORT y,
         USHORT fsTrack)
 {
     RECTL rclFill;

     VOID CalcTrackRect(PRECTL, PRECTL, SHORT, USHORT);

     if (fsTrack & SPS_VERT) {
         CalcTrackRect(prclClient, (PRECTL)&rclFill, x, SPS_VERT);

         InvertRect(hps, (PRECTL)&rclFill);
     }

     if (fsTrack & SPS_HORZ) {
         CalcTrackRect(prclClient, (PRECTL)&rclFill, y, SPS_HORZ);

         InvertRect(hps, (PRECTL)&rclFill);
     }
 }


 VOID MoveTrackRects(HPS hps, PRECTL prclClient, USHORT fsTrack, SHORT xOld,
         SHORT yOld, SHORT xNew, SHORT yNew)
 {
     RECTL rclOld, rclNew, rclScratch;
     VOID CalcTrackRect(PRECTL, PRECTL, SHORT, USHORT);

     if (fsTrack & SPS_VERT) {
         CalcTrackRect(prclClient, (PRECTL)&rclOld, xOld, SPS_VERT);
         CalcTrackRect(prclClient, (PRECTL)&rclNew, xNew, SPS_VERT);

         if (WinSubtractRect(NULL, &rclScratch, &rclOld, &rclNew))
             InvertRect(hps, (PRECTL)&rclScratch);
         if (WinSubtractRect(NULL, &rclScratch, &rclNew, &rclOld))
             InvertRect(hps, (PRECTL)&rclScratch);
     }

     if (fsTrack & SPS_HORZ) {
         CalcTrackRect(prclClient, (PRECTL)&rclOld, yOld, SPS_HORZ);
         CalcTrackRect(prclClient, (PRECTL)&rclNew, yNew, SPS_HORZ);

         if (WinSubtractRect(NULL, &rclScratch, &rclOld, &rclNew))
             InvertRect(hps, (PRECTL)&rclScratch);
         if (WinSubtractRect(NULL, &rclScratch, &rclNew, &rclOld))
             InvertRect(hps, (PRECTL)&rclScratch);
     }
 }


 VOID InvertRect(HPS hps, PRECTL prcl)
 {
     RECTL rclParm[2];

     WinCopyRect(NULL, (PRECTL)&rclParm[0], (PRECTL)prcl);
     rclParm[1].xLeft = 0;
     rclParm[1].yBottom = 0;
     GpiBitBlt(hps, (HPS)NULL, 3L, (PPOINTL)rclParm, ROP_PATINVERT, 0L);
 }


 VOID FindSwp(PSWP aswp, register USHORT cswp, USHORT id, PSWP FAR *ppswp)
 {
     register i;

     for (i = 0; i < (SHORT) cswp; i++) {
         if (WinQueryWindowUShort(aswp[i].hwnd, QWS_ID) == id) {
             *ppswp = &aswp[i];
             return;
         }
     }
 }


 /*
  * Set the SWP structure to the passed in parameters.
  *
  * This routine is dependent on the order of elements
  * in the SWP structure.
  */
 VOID SetSwpPos(PSWP pswp, HWND hwnd, HWND hwndInsertBehind, SHORT x, SHORT y,
         SHORT cx, SHORT cy, USHORT fs)
 {
     /*
         *pswp = *((PSWP)&fs);
     */
     pswp->hwnd                        =        hwnd;
     pswp->hwndInsertBehind        =        hwndInsertBehind;
     pswp->x                        =        x;
     pswp->y                        =        y;
     pswp->cx                        =        cx;
     pswp->cy                        =        cy;
     pswp->fs                        =        fs;
 }


 VOID HideSwp(PSWP pswp, HWND hwnd, USHORT *piswp)
 {
     if (WinIsWindowVisible(hwnd)) {
         SetSwpPos(pswp, hwnd, NULL, 0, 0, 0, 0, SWP_HIDE);
         (*piswp)++;
     }
 }


 MEM.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\STOCK\MEM.C

 /***        mem.c - memory definitions
  *
  *        routines that layer C run-time library memory
  *        routines to provide debugging assistance.
  *
  *        History
  *            07-Mar-1990 bens        Initial version.
  *            08-Mar-1990 bens        Added heap check code
  *            16-Mar-1990 bens        MyAlloc should return NULL on failure!
  */
 #ifdef CHECKASSERTS

 #define INCL_WINWINDOWMGR

 #include <os2.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <memory.h>

 #include "mem.h"

 typedef ULONG MEMSIG;         // ms
 typedef MEMSIG *PMEMSIG; // pms

 typedef struct mh_s {         // mh
     MEMSIG            ms;                 // Head signature
     USHORT            cb;                 // Size of user block
     struct mh_s *   pmhNext;                // Next block
     struct mh_s *   pmhPrev;                // Previous block
     // char ach[?];                        // User block; length is cb
     // MEMSIG ms[?];                        // Tail signature area
 } MEMHDR;
 typedef MEMHDR *PMEMHDR; // pmh

 #define MEMSIG_HEAD    0x12345678L
 #define MEMSIG_TAIL    0x87654321L
 #define MEMSIG_BAD     0L
 #define TAIL_SIZE   2                        // 8-byte tail

 void FailAssert(char *sz);

 char achBug[CCHMAXPATH];                // Buffer for formatting errors
 PMEMHDR pmhList=NULL;                        // List of memory blocks


 /***        FailAssert - common exit for assertion failure
 *
 *        ENTRY        sz - message string
 *
 *        EXIT        issue message and exit
 */
 void FailAssert(char *sz)
 {
     WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, sz,
         "Assertion Failure", 0, MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL);
     DosExit(1,2);
 }


 void MyCheckHeap(char *pszFile,USHORT line)
 {
     PMEMHDR pmh;

     for (pmh = pmhList; pmh != NULL; pmh = pmh->pmhNext)
         MyAssert((char *)pmh+sizeof(MEMHDR),pszFile,line);
 }


 void MyAssert(void *pv,char *pszFile,USHORT line)
 {
     USHORT  cb;
     int     i;
     PMEMHDR pmh;
     PMEMSIG pms;

     pmh = (PMEMHDR)((char *)pv - sizeof(MEMHDR));
     if (pmh > pv) {                        // Pointer wrapped
         sprintf(achBug,"%s:%d: panic: NULL pointer(?)",pszFile,line);
         FailAssert(achBug);
     }

     // Test head signature
     if (pmh->ms != MEMSIG_HEAD) {
         sprintf(achBug,"%s:%d: panic: memory header trashed",pszFile,line);
         FailAssert(achBug);
     } else {
         // Test tail signatures
         pms = (PMEMSIG)( (char *)pmh + sizeof(MEMHDR) + pmh->cb );
         for (i=0; i<TAIL_SIZE; i++) {
             if (*pms++ != MEMSIG_TAIL) {
                 sprintf(achBug,"%s:%d: panic: memory tail trashed",pszFile,li
                 FailAssert(achBug);
             }
         }
     }

 }


 void MyFree(void *pv,char *pszFile,USHORT line)
 {
     PMEMHDR pmh;

     MyAssert(pv,pszFile,line);

     pmh = (PMEMHDR)((char *)pv - sizeof(MEMHDR));

     // Make previous block point to next block
     if (pmh->pmhPrev != NULL) {         // pmh is not at front of list
         // before: a->p->?
         pmh->pmhPrev->pmhNext = pmh->pmhNext;
         // after:  a->?
     }
     else {                                // pmh is at front of list
         // before: list->p->?
         pmhList = pmh->pmhNext;
         // after: list->?
     }

     // Make next block point to previous block
     if (pmh->pmhNext != NULL) {         // pmh is not at end of list
         // before: ?<-p<->a
         pmh->pmhNext->pmhPrev = pmh->pmhPrev;
         // after:  ?<-a
     }

     // Obliterate signature
     pmh->ms = MEMSIG_BAD;

     // Free memory
     free((char *)pmh);
 }


 void *MyAlloc(USHORT cb,char *pszFile,USHORT line)
 {
     USHORT        cbAlloc;
     int         i;
     char *        pch;
     PMEMHDR        pmh;
     PMEMSIG        pms;

     MyCheckHeap(pszFile,line);

     cbAlloc = cb+sizeof(MEMHDR)+sizeof(MEMSIG_TAIL)*TAIL_SIZE;
     pmh = malloc(cbAlloc);
     if (pmh != NULL) {
         pmh->ms = MEMSIG_HEAD;                // Store head signature
         pmh->cb = cb;                        // Store size of user block

         // Add block to front of list (Easiest code!)
         if (pmhList != NULL)                // List is not empty
             pmhList->pmhPrev = pmh;        // Point old top block back at us
         pmh->pmhNext = pmhList;         // Next element is old top block
         pmh->pmhPrev = NULL;                // We are first, so no prev block
         pmhList = pmh;                        // Make ourselves first

         // Fill in tail signatures
         pms = (PMEMSIG)( (char *)pmh + sizeof(MEMHDR) + pmh->cb );
         for (i=0; i<TAIL_SIZE; i++)
             *pms++ = MEMSIG_TAIL;
         return ((char *)pmh + sizeof(MEMHDR));
     }
     else {
         printf("panic: out of memory in MyAlloc\n");
         printf("\n");
         printf("Dump of heap (newest alloc to oldest)\n");
         printf("\n");
         printf("Size  Addr Content\n");
         printf("----- ---- -------\n");
         for (pmh = pmhList; pmh != NULL; pmh = pmh->pmhNext) {
             pch = (char *)pmh + sizeof(MEMHDR);
             printf("%5d %04x %s\n",pmh->cb,(USHORT)pch,pch);
         }
         return NULL;
     }
 }


 char *MyStrDup(char *pch,char *pszFile,USHORT line)
 {
     USHORT  cb;
     char *  pchDst;

     cb = strlen(pch)+1;                 // Count NUL terminator
     pchDst = MyAlloc(cb,pszFile,line);        // Alloc new copy
     if (pchDst != NULL) {                //  Success
         memcpy(pchDst,pch,cb);                // Copy string
     }
     return pchDst;                        // Return string copy
 }
 #endif


 MEM.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MEM.C

 #include "ddemlp.h"

 /*
  * Memory routines
  *
  *  These routines simplify and centralize memory handling to help reduce
  *  selector usage overhead and ease transport to 32 bit memory models.
  */


 /*
  * Debugging routines --------------------------------------------------
  */

 PBYTE FarAllocMem(hheap, cb)
 HHEAP hheap;
 USHORT cb;
 {
     register NPBYTE pb;

     pb = MyAllocMem(hheap, cb);
     if (pb == NULL)
         return(NULL);
     return(MAKEP(SELECTOROF(hheap), pb));
 }


 #ifdef DEBUG

 #define MAX_SQUIRL 400
 #define HPSIG 0xfedc

 PUSHORT asqu[MAX_SQUIRL] = { NULL };
 USHORT cMaxSqu = 0;
 USHORT cCurSqu = 0;

 VOID Walk(VOID);
 VOID Squirl(PBYTE);
 VOID Unsquirl(PBYTE);


 VOID Walk()
 {
     static BOOL fRecurse = FALSE;
     register USHORT i;

     if (fRecurse)
         return;
     fRecurse = TRUE;
     for (i = 0; i < cMaxSqu; i++) {
         if (OFFSETOF(asqu[i]) < CheckSel(SELECTOROF(asqu[i])))
             if (!(*asqu[i] == HPSIG &&
                     (*(PUSHORT)((PBYTE)asqu[i] +
                     (*(asqu[i] - 1)) - 2) == HPSIG))) {
                 AssertF(FALSE, "Heap Walk failure");
                 asqu[i] = NULL;
             }
     }
     fRecurse = FALSE;
 }

 VOID Squirl(p)
 PBYTE p;
 {
     register USHORT i;

     for (i = 0; i < cMaxSqu + 1; i++) {
         if (asqu[i] == NULL) {
             asqu[i] = (PUSHORT)p;
             cCurSqu = min(cCurSqu + 1, MAX_SQUIRL - 1);
             cMaxSqu = max(cCurSqu, cMaxSqu);
             return;
         }
     }
 }

 VOID Unsquirl(p)
 PBYTE p;
 {
     register USHORT i;

     for (i = 0; i < cMaxSqu + 1; i++) {
         if (asqu[i] == (PUSHORT)p) {
             asqu[i] = NULL;
             cCurSqu--;
             return;
         }
     }
     AssertF(cMaxSqu == (MAX_SQUIRL - 1), "Unsquirl error");
 }


 HHEAP DbgCreateHeap(
 SEL sel,
 USHORT cb,
 USHORT cbGrow,
 USHORT cbMinDed,
 USHORT cbMaxDed,
 USHORT fs)
 {
     HHEAP hheap;
     register USHORT i;

     hheap = WinCreateHeap(sel, cb, cbGrow, cbMinDed, cbMaxDed, fs);
     sel = HIUSHORT(WinLockHeap(hheap));
     /*
      * remove all squirled pointers in this segment so we don't
      * get errors later.
      */
     for (i = 0; i < cMaxSqu; i++) {
         if (SELECTOROF(asqu[i]) == sel) {
             asqu[i] = NULL;
             cCurSqu--;
         }
     }
     return(hheap);
 }



 HHEAP DbgDestroyHeap(
 HHEAP hheap)
 {
     /*
      * purge squirl list of any of these pointers.
      */
     register USHORT i;

     for (i = 0; i < cMaxSqu; i++) {
         if (SELECTOROF(asqu[i]) == SELECTOROF(hheap)) {
             asqu[i] = NULL;
             cCurSqu--;
         }
     }
     return(WinDestroyHeap(hheap));
 }



 NPBYTE DbgAllocMem(hheap, cb)
 HHEAP hheap;
 USHORT cb;
 {
     register PBYTE p;

     Walk();
     p = MAKEP(SELECTOROF(hheap), WinAllocMem(hheap, cb + 4));
     if (LOUSHORT(p) == NULL)
         return(NULL);
     Squirl(p);
     *(PUSHORT)p = HPSIG;
     *(PUSHORT)(p + cb + 2) = HPSIG;
     return((NPBYTE)(SHORT)p + 2);
 }


 /*
  * note that cbOld perameter includes the signatures since it was derived
  * from the heap itself.  cbNew, however does not incude the signatures.
  */
 NPBYTE DbgReallocMem(hheap, npMem, cbOld, cbNew)
 HHEAP hheap;
 NPBYTE npMem;
 USHORT cbOld;
 USHORT cbNew;
 {
     register PBYTE p;

     Walk();
     p = MAKEP(SELECTOROF(hheap), npMem - 2);
     *(PUSHORT)p = 0;
     *(PUSHORT)(p + cbOld - 2) = 0;
     Unsquirl(p);
     p = MAKEP(SELECTOROF(hheap), WinReallocMem(hheap, npMem - 2, cbOld, cbNew
     if (LOUSHORT(p) == NULL) {
         AssertF(FALSE, "Reallocation failure");
         return(NULL);
     }
     Squirl(p);
     *(PUSHORT)p = HPSIG;
     *(PUSHORT)(p + cbNew + 2) = HPSIG;
     return((NPBYTE)(SHORT)p + 2);
 }



 /*
  * note that cbMem perameter includes the signatures since it was derived
  * from the heap itself.
  */
 NPBYTE DbgFreeMem(hheap, npMem, cbMem)
 HHEAP hheap;
 NPBYTE npMem;
 USHORT cbMem;
 {
     register PBYTE p;

     Walk();
     p = MAKEP(SELECTOROF(hheap), npMem - 2);
     Unsquirl(p);
     cbMem += 4;
     AssertF(cbMem == *(PUSHORT)(p - 2), "FreeMem:Incorrect cbMem perameter")

     if (*(PUSHORT)p != HPSIG || *(PUSHORT)(p + cbMem - 2) != HPSIG)
         AssertF(FALSE, "Freed memory is corrupted");
     *(PUSHORT)p = 0;
     *(PUSHORT)(p + cbMem - 2) = 0;
     p = (PBYTE)WinFreeMem(hheap, npMem - 2, cbMem);
     if (LOUSHORT(p) != 0)
         AssertF(FALSE, "Memory freeing failed");
     return((NPBYTE)LOUSHORT(p));
 }

 /* DEBUG ------------------------------------------------------   */


 MESSAGE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\MESSAGE.C

 /***************************************************************************\
 * message.c - procecures to process the message dialog
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define        INCL_WINBUTTONS
 #define        INCL_WINDIALOGS
 #define        INCL_WINFRAMEMGR
        INCL_WINHEAP                        /* needed for spy.h */
 #define        INCL_WININPUT
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMESSAGEMGR
 #define        INCL_WINMENUS
 #define        INCL_WINPOINTERS
 #define INCL_WINWINDOWMGR
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include "spy.h"
 #include "spyhook.h"

 #define SIZEOFWND   34
 #define MAXMSGBYTES 100

 /* Procedure prototypes */
 void    UpdMsgsLBSels (USHORT, BOOL);
 void    UpdMsgTblFromLB (HWND);
 void    ProcessQueueMsg(QMSGSPY *);
 void    SelOrDeselWithMouse(BOOL);
 void    UpdateMsgBoxCurMsgText(HWND);
 void    SelectMessageFromText(HWND);
 MRESULT        EXPENTRY SpyMsgDlgProc(HWND, USHORT, MPARAM, MPARAM);

 /***************************************************************************\
 * MRESULT EXPENTRY SpyMsgDlgProc (hwnd, msg, mp1, mp2)
 *
 * Message List dialog procedure
 \***************************************************************************/
 MRESULT EXPENTRY SpyMsgDlgProc(hwnd, msg, mp1, mp2)
 HWND            hwnd;
 USHORT          msg;
 MPARAM          mp1;
 MPARAM          mp2;
 {
     SHORT       i;
     MSGI        *pmsgi;
     USHORT      item;
     SHORT       bHooksNew;
     USHORT      iItemFocus; /* Index to item that has the focus */

     switch (msg) {

     case WM_INITDLG:
         /*
          * Initialize the list box with the list of messages that are
          * defined in our message table
          */
         iCurItemFocus = -1;
         pmsgi = rgmsgi; /* Point to start of list */
         hwndMessageLB = WinWindowFromID(hwnd, DID_OMSGLIST);
         for (i = 0; i < cmsgi; i++) {
             pmsgi->iListBox = item = SHORT1FROMMR(WinSendMsg(hwndMessageLB,
                     LM_INSERTITEM,
                     (MPARAM)(spyopt.fAlphaSortMsgList? LIT_SORTASCENDING : LI
                     (MPARAM)(PSZ)pmsgi->szMsg));
             WinSendMsg(hwndMessageLB, LM_SETITEMHANDLE, (MPARAM)item,
                 (MPARAM)i);
             if (pmsgi->wOptions & MSGI_ENABLED) {
                 WinSendMsg(hwndMessageLB, LM_SELECTITEM, (MPARAM)item,
                 (MPARAM)TRUE);
             }
             pmsgi++;
         }

         /* Initialize the Hook type record */
         WinSendDlgItemMsg(hwnd, DID_OINPUT, BM_SETCHECK,
                 (MPARAM)(spyopt.bHooks & SPYH_INPUT)?1 : 0, 0L);

         WinSendDlgItemMsg(hwnd, DID_OSENDMSG, BM_SETCHECK,
                 (MPARAM)(spyopt.bHooks & SPYH_SENDMSG)?1 : 0, 0L);

         WinSendDlgItemMsg(hwnd, DID_OTHERMSGS, BM_SETCHECK,
                 (MPARAM)(spyopt.fDispOtherMsgs), 0L);

         WinSetFocus(HWND_DESKTOP, hwndMessageLB);
         fTrackingListBox = TRUE;
         return ((MRESULT)TRUE);  /* We set the focus */

         break;

     case WM_CHAR:
         /*
          * Handle VK_ENTER and VK_NEWLINE if our Edit control has
          * the focus and it is a keydown
          */
         if (!(SHORT1FROMMP(mp1) & KC_KEYUP) &&
                 (SHORT1FROMMP(mp1) & KC_VIRTUALKEY) &&
                 ( (SHORT2FROMMP(mp2) == VK_ENTER) ||
                   (SHORT2FROMMP(mp2) == VK_NEWLINE) )) {


             if (WinQueryFocus(HWND_DESKTOP, FALSE) ==
                     WinWindowFromID(hwnd, DID_MSGEDIT)) {
                 SelectMessageFromText(hwnd);
                 break;
             }
         }

         /* Normaly pass to dialog procedure to handle message */
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:

             /*
              * Call to update the Message table select bits
              */

             UpdMsgTblFromLB (hwnd);

             /* Setup new hook options */
             bHooksNew = 0;

             if ((BOOL)(SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
                     DID_OINPUT, BM_QUERYCHECK, 0L, 0L))))
                 bHooksNew = SPYH_INPUT;

             if ((BOOL)(SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
                     DID_OSENDMSG, BM_QUERYCHECK, 0L, 0L))))
                 bHooksNew |= SPYH_SENDMSG;

             if (bHooksNew != (SHORT) spyopt.bHooks) {
                 SpyReleaseHook (FALSE);     /* Dont clear queue */
                 spyopt.bHooks = bHooksNew;
                 SpyInstallHook(hab, hmqSpy, spyopt.bHooks); /* Install hook a
                 WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                     MM_SETITEMATTR, MPFROM2SHORT(CMD_INPUTHOOK, TRUE),
                     MPFROM2SHORT(MIA_CHECKED,
                          (spyopt.bHooks & SPYH_INPUT) ? MIA_CHECKED : 0));
                 WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                     MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDMSGHOOK, TRUE),
                     MPFROM2SHORT(MIA_CHECKED,
                          (spyopt.bHooks & SPYH_SENDMSG) ? MIA_CHECKED : 0));
             }

             /* Fall through to DID_CANCEL */
         case DID_CANCEL:
             /* Now dismiss the dialog */
             hwndMessageLB = NULL;   /* Not here anymore to process */
             fTrackingListBox = FALSE;
             WinDismissDlg(hwnd, SHORT1FROMMP(mp1));
             break;

         /*
          * These case simply update the listbox with which messages are
          * enabled
          */
         case DID_MALL:
             UpdMsgsLBSels (0, TRUE);
             break;
         case DID_MNONE:
             UpdMsgsLBSels (0, FALSE);
             break;
         case DID_MCON:
             UpdMsgsLBSels (MSGI_KEY, TRUE);
             break;
         case DID_MCOFF:
             UpdMsgsLBSels (MSGI_KEY, FALSE);
             break;
         case DID_MMON:
             UpdMsgsLBSels (MSGI_MOUSE, TRUE);
             break;
         case DID_MMOFF:
             UpdMsgsLBSels (MSGI_MOUSE, FALSE);
             break;
         case DID_MFON:
             UpdMsgsLBSels (MSGI_FREQ, TRUE);
             break;
         case DID_MFOFF:
             UpdMsgsLBSels (MSGI_FREQ, FALSE);
             break;
         }
         break;

     default:
         /*
          * Default is to see if the listbox has changed its focus
          * item number.  If it has, then we want to display the information
          * about the window that the listbox cursor is over.  There is no
          * legal way to do this, except to temporarily put the listbox into
          * single selection mode and query the selection.
          */
         if (fTrackingListBox && (hwndMessageLB != NULL)) {
             WinSetWindowBits(hwndMessageLB, QWL_STYLE, 0L, LS_MULTIPLESEL);
             iItemFocus = SHORT1FROMMR(WinSendMsg(hwndMessageLB, LM_QUERYSELEC
                     (MPARAM)LIT_FIRST, 0L));
             WinSetWindowBits(hwndMessageLB, QWL_STYLE, LS_MULTIPLESEL,
                     LS_MULTIPLESEL);
             if (iItemFocus != iCurItemFocus) {
                 iCurItemFocus = iItemFocus;
                 UpdateMsgBoxCurMsgText(hwnd);
             }
         }
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return 0L;
 }



 /***************************************************************************\
 * void UpdateMsgBoxCurMsgText()
 *
 *   Updates the text that is displayed in the message text line
 \***************************************************************************/
 void UpdateMsgBoxCurMsgText(hwndDlg)
 HWND    hwndDlg;
 {
     SHORT   sMsgI;
     char    szTemp[80];


     if (iCurItemFocus >= 0) {
         /* Get the messge index */
         sMsgI = SHORT1FROMMR(WinSendMsg(hwndMessageLB, LM_QUERYITEMHANDLE,
                 (MPARAM)iCurItemFocus, 0L));
         sprintf(szTemp, "0x%04x  - %s", rgmsgi[sMsgI].msg,
                 rgmsgi[sMsgI].szMsg);
         WinSetDlgItemText(hwndDlg, DID_MSGEDIT, (PSZ)szTemp);
     } else {
         WinSetDlgItemText(hwndDlg, DID_MSGEDIT, (PSZ)"");
     }

 }




 /***************************************************************************\
 * void SelectMessageFromText(hwndDlg)
 *
 *   Updates the text that is displayed in the message text line
 \***************************************************************************/
 void SelectMessageFromText(hwndDlg)
 HWND    hwndDlg;
 {
     char    szTemp[80];
     USHORT  msg;
     MSGI    *pmsgi;
     int     i;


     /* First get the edit text from the string */
     WinQueryDlgItemText(hwndDlg, DID_MSGEDIT, sizeof(szTemp),
             (PSZ)szTemp);

     if ((msg = UConvertStringToNum(szTemp)) != 0xffff) {

         /* We have a number, now try to find message in message table */
         pmsgi = PmsgiFromMsg(msg);

     } else {
         /* Assume String, Try to locate string in our table */
         pmsgi = rgmsgi; /* Start at beginning of table */
         /*
          * This does simple string compares, it does not map case, nor
          * does it trim the string.
          */
         for (i=0; i < cmsgi; i++) {
             if (strcmpi(pmsgi->szMsg, szTemp) == 0)
                 break;
             pmsgi++;
         };

         if (i >= cmsgi)
             pmsgi = NULL;
     }

     /*
      * Have a pointer to MSGI of message, or NULL if not in list.
      */
     if(pmsgi != NULL) {
         /* First make sure it is visible */
         WinSendMsg(hwndMessageLB, LM_SETTOPINDEX,
                 MPFROMSHORT(pmsgi->iListBox),  (MPARAM)0L);

         /* Always set it on */
         WinSendMsg(hwndMessageLB, LM_SELECTITEM,
                 MPFROMSHORT(pmsgi->iListBox), MPFROMSHORT(TRUE));

     } else {
         WinAlarm(HWND_DESKTOP, WA_WARNING);
         WinSetDlgItemText(hwndDlg, DID_MSGEDIT, (PSZ)"");
     }
 }





 /***************************************************************************\
 * void UpdMsgsLBSels (USHORT uMask, fOnOrOff);
 *
 * Will update the selected items in the message listbox, that is
 *   displayed in the options dialog
 \***************************************************************************/
 void UpdMsgsLBSels (uMask, fOnOrOff)
 USHORT      uMask;
 BOOL        fOnOrOff;
 {

     SHORT       i;
     MSGI        *pmsgi;

     /*
      * Loop through all of the items in our list, if the mask is 0 or
      * the bit is on in the item, then update the select state in listbox
      * defined in our message table
      */
     fTrackingListBox = FALSE;
     pmsgi = rgmsgi; /* Point to start of list */
     for (i = 0; i < cmsgi; i++) {
         if ((uMask == 0) || (pmsgi->wOptions & uMask)) {
             WinSendMsg(hwndMessageLB, LM_SELECTITEM,
         (MPARAM)pmsgi->iListBox,  (MPARAM)fOnOrOff);
         }
         pmsgi++;
     }
     fTrackingListBox = TRUE;
 }



 /***************************************************************************\
 * void UpdMsgTblFromLB (HWND hwndDialog);
 *
 * Will update the selected items in the message listbox, that is
 *   displayed in the options dialog
 \***************************************************************************/
 void UpdMsgTblFromLB (hwndDialog)
 HWND            hwndDialog;
 {

     USHORT       i;
     register MSGI *pmsgi;
     USHORT      itemSel;

     /*
      * Loop through all of the items in the list and update the selection
      * status depending of if the item is selected in the list box or
      * not.
      */
     /* First simply turn off all of the bits */
     pmsgi = rgmsgi;
     for (i = 0; i < (USHORT) cmsgi; i++) {
         pmsgi->wOptions &= ~MSGI_ENABLED;
         pmsgi++;
     }

     /* Then turn on all of the selected items */
     itemSel = (USHORT)LIT_FIRST;

     while ((itemSel = SHORT1FROMMR(WinSendMsg(hwndMessageLB, LM_QUERYSELECTIO
             (MPARAM)itemSel, 0L))) != (USHORT)LIT_NONE) {

         /* The item handle contains index in our array */
         i = SHORT1FROMMR(WinSendMsg(hwndMessageLB, LM_QUERYITEMHANDLE,
             (MPARAM)itemSel, 0L));

         rgmsgi[i].wOptions |= MSGI_ENABLED;
     }

     /* Get the Other message option from checkmark */
     spyopt.fDispOtherMsgs = ((BOOL)SHORT1FROMMR(WinSendDlgItemMsg(hwndDialog,
             BM_QUERYCHECK, 0L, 0L)));

     /* Now call function to update the hooks message list */
     UpdateHooksMsgTable();
 }




 /***************************************************************************\
 * void UpdateHooksMsgTable(void)
 *
 * Send the message bitmask to the hook, for interesting messages.
 \***************************************************************************/
 void  UpdateHooksMsgTable()
 {
     MSGI            *pmsgi;
     UCHAR           rgb[MAXMSGFILTERBYTES];
     int             i;
     UCHAR           *prgb;
     unsigned char   mask;

     /*
      * First zero the bitmask
      */
     memset(rgb,'\0', MAXMSGFILTERBYTES);
     mask = 1;
     prgb = rgb;

     /*
      * Loop to set the bits
      */
     pmsgi = rgmsgi;
     for (i = 0; i <= MAXMSGFILTER; i++) {
         /* If enabled, set bit in bit table */

         if ((SHORT) pmsgi->msg == i) {
             if (pmsgi->wOptions & MSGI_ENABLED)
                 *prgb =(UCHAR)(pmsgi->bMPTypes | MP_ENABLED);

             pmsgi++;
         } else {
             /* Hole in range, set it true */
             *prgb |= mask;
         }

         prgb++;
     }

     /* Now call the hook function with the new mask */
     SpySetMessageList((char far *)rgb, spyopt.fDispOtherMsgs);
 }




 /***************************************************************************\
 * EnableOrDisableMsg(BOOL fEnable)
 *
 * Fast way to enable or disable a particular message code.  The one
 *           that is currently selected in the output listbox.
 \***************************************************************************/

 void EnableOrDisableMsg(fEnable)
 BOOL            fEnable;
 {
     USHORT      itemSel;
     char        szTemp[100];
     char        *psz;
     MSGI        *pmsgi;
     SHORT       i;

     itemSel = SHORT1FROMMR(WinSendMsg(hwndSpyList, LM_QUERYSELECTION,
             (MPARAM)LIT_FIRST, 0L));
     if (itemSel == (USHORT)LIT_NONE)
         return;    /* None to process */

     /* Get the message text */
     WinSendMsg(hwndSpyList, LM_QUERYITEMTEXT,
             MPFROM2SHORT(itemSel, sizeof(szTemp)), (MPARAM)(PSZ)szTemp);

     /* Now lets extract the messgae string from the line */
     psz = &szTemp[3];
     while (*psz != ' ')
         psz++;    /* locate first blank */
     *psz = '\0';    /* Zero terminate string */


     /*
      * Loop through all of the items in our list, until we find a
      * string that matches our string.
      */
     pmsgi = rgmsgi; /* Point to start of list */
     for (i = 0; i < cmsgi; i++) {
         if (strcmpi(&szTemp[2], pmsgi->szMsg) == 0) {
             /*
              * Found our message, update the bit of the message, and
              * call the function to let the hook know the new results
              */
             if (fEnable)
                 pmsgi->wOptions |= MSGI_ENABLED;
             else
                 pmsgi->wOptions &= ~MSGI_ENABLED;

                UpdateHooksMsgTable();      /* Set Spy's Msg table */
             return;
         }
         pmsgi++;
     }
 }


 MODEM.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TERMINAL\MODEM.C

 /***        modem.c
  *
  * TITLE
  *
  *        modem.c
  *        Created by Microsoft Corporation 1987
  *
  * DESCRIPTION
  *
  *         This module contains routines that support Hayes compatible modem.
  *
  ***/

 #define     INCL_DOSDEVICES
 #define     INCL_DOSFILEMGR
 #define     INCL_DOSPROCESS

 #include        <os2def.h>
 #include        <bse.h>
 #include        <stdio.h>
 #include        <malloc.h>
 #include        <string.h>
 #include         <memory.h>
 #include         <conio.h>
 #include         "term.h"

 min(a,b) (((a)<(b)) ? (a):(b))        /* This is pulled out of the old */
                                         /* v2tov3.h (c v2.0) header file */

 extern char         *ErrMsg[];
 extern HFILE FileHndl;

 make_dial_cmd(char *, int);
 void flush_ques(USHORT);
 void clr_com_error(void);
 void com_clr_flush(void);

 static char Init[]    = {'A', 'T', 'V', '0', 0x0d};
 static char Attn[]    = {'A', 'T', 0x0d};
 static char Esc[]     = {'+', '+', '+'};
 static char OnHook[]  = {'A', 'T', 'H', '0', 0X0d};
 static char OffHook[] = {'A', 'T', 'H', '1', 0x0d};
 static char Reset[]   = {'A', 'T', 'Z', 0x0d};

 struct s_Cmd{
         char *pCmd;
         int   CmdLen;
 } Cmds[] = {
     {Init, sizeof(Init)},                /* Initialize modem */
     {Attn, sizeof(Attn)},                /* Attention cmd */
     {Esc, sizeof(Esc)},                  /* Modem escape sequence*/
     {OnHook, sizeof(OnHook)},            /* Put phone on hook */
     {OffHook, sizeof(OffHook)},          /* Take phone off hook */
     {Reset, sizeof(Reset)}               /* Reset command */
   };



 /***        make_modem_conn - make modem connection
  *
  *        This routine tries to setup the modem connection, retrying if
  *        appropriate.
  *
  *        make_modem_conn()
  *
  *        ENTRY
  *
  *        EXIT
  *                TRUE if modem connection was made
  *                FALSE if modem connection did not go through
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 make_modem_conn()
 {
         USHORT                NumBytes;        /* number of bytes to be writt
         unsigned        RetCode,        /* return code from system calls    *
                         Result = FALSE; /* to be returned by this routine */
         char                OutBuffer,
                         ModemRetry = TRUE,   /* retry for modem connection
                         NumRetries = 0;      /* no. times connection retried

         printf("trying modem connection...\n");

           while (ModemRetry)
           switch (setup_modem_conn()) {
             case MS_CONNECT    :
             case MS_CONNECT1200:
               Result = TRUE;
               ModemRetry = FALSE;
               OutBuffer = '\r';
               if ((RetCode = DosWrite(FileHndl, &OutBuffer, 1,
                                       &NumBytes)) != 0)
                 error(ERR_DOSWRITE, RetCode);
               break;
             case MS_NOCARRIER  :
             case MS_NODIALTONE :
               if (++NumRetries > NUM_RETRY) {
                 printf("modem connection failed\n");
                 ModemRetry = FALSE;
               }
               else
                 printf("modem connection failed; retrying...\n");
               break;
             case MS_NOREPLY    :
             case MS_ERROR      :
             case OT_FAILURE    :                /* OTher failure */
             case MS_BUSY       :
             default            :
               ModemRetry = FALSE;
               printf("modem connection failed\n");
               break;
           }                /* switch (setup_modem_conn()) */

         return(Result);

 }




 /***        setup_modem_conn - setup modem connection
  *
  *        This is the helper routine for the make_modem_conn(). It initialise
  *        the modem connection and sends out the dial command to the modem.
  *
  *        setup_modem_conn()
  *
  *        ENTRY
  *
  *        EXIT
  *                returns one of the following: MS_CONNECT MS_CONNECT1200
  *                    MS_NOCARRIER MS_NODIALTONE MS_NOREPLY MS_ERROR MS_BUSY
  *                    OT_FAILURE
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 int        setup_modem_conn()
 {
         int                Result = OT_FAILURE;

         /* initialise modem connection */
         if (!(send_modem_cmd(MC_INIT)))
           return(Result);
         if (get_modem_reply() != MS_OK)
           return(Result);
         DosSleep(100L);

         /* write AT string to the modem. This will cause the modem to
          * determine the communications rate of the terminal as well
          * as its parity setting.
           */
         if (!(send_modem_cmd(MC_ATTN)))
           return(Result);
         if (get_modem_reply() != MS_OK)
           return(Result);
         DosSleep(100L);

         /* send DIAL command to the modem */
         if (!(send_modem_cmd(MC_DIAL)))
           return(Result);

         return(get_modem_reply());
 };




 /***        send_modem_cmd - send a command to modem
  *
  *        This routine sends a command to the modem and ensures that the echo
  *        from the modem matches the input command.
  *
  *        send_modem_cmd(CmdType)
  *
  *        ENTRY
  *                CmdType - type of modem command: MC_INIT MC_ATTN MC_ESC
  *                                  MC_ONHOOK MC_OFFHOOK MC_RESET MC_DIAL
  *
  *        EXIT
  *                TRUE if command was sent succesfully
  *                FALSE if command failed
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 send_modem_cmd(CmdType)
 int         CmdType;                    /* The command to send */
 {
             int                  Result = TRUE,
                         RetCode;
         USHORT                WriteCnt,
                         ReadCnt,
                         CmdLen;
             char                 Buf[80],
                             Cmd[80],
                             *pDst,
                             *pCmd;


         /* clear COM error and flush transmit/receive queues */
         com_clr_flush();

         if (CmdType == MC_INIT) {
           send_modem_cmd(MC_RESET);        /* modem command: ATZ */
           /* clear COM error and flush transmit/receive queues */
           com_clr_flush();
         };

         /* set pCmd -> command string; CmdLen = length of command string */
             if (CmdType == MC_DIAL) {
           CmdLen = make_dial_cmd(Cmd, sizeof(Cmd));
           pCmd = Cmd;
         }
             else {
           pCmd = Cmds[CmdType].pCmd;
           CmdLen = Cmds[CmdType].CmdLen;
         };

         /* write the command out to the modem */
         if (((RetCode = DosWrite(FileHndl, pCmd, CmdLen, &WriteCnt)) != 0)
             || (WriteCnt != CmdLen))
           return(FALSE);

         /* wait for the echo & wait for the command to clear */
         DosSleep(250L);
         if (CmdType == MC_DIAL || CmdType == MC_RESET)
           DosSleep(750L);

         /* read back the echo from the modem & check if it matches the input
         RetCode =
           DosRead(FileHndl, Buf, min(WriteCnt, sizeof(Buf)), &ReadCnt);
         if ((RetCode != 0) || (ReadCnt != WriteCnt))
           Result = FALSE;
         else {
           pDst = Buf;
           while (--ReadCnt >= 0) {
             if (*pDst++ != *pCmd++) {
               printf("send_modem_cmd: error in echo of modem input\n");
               Result = FALSE;
               break;
             }
           } /* while (--ReadCnt >= 0) */
         }

             return(Result);
 }




 /***        make_dial_cmd - create a modem command for dialing
  *
  *        builds the dial command string and returns its length in bytes
  *
  *        make_dial_cmd(pBuf, BufLen)
  *
  *        ENTRY
  *                pBuf - ptr to buffer
  *                BufLen - length of buffer in bytes
  *
  *        EXIT
  *                pBuf -> dial command string
  *                make_dial_cmd = length of dial command string in bytes
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 make_dial_cmd(pBuf, BufLen)
 char         *pBuf;
 int          BufLen;
 {
             char                 *pSrc,
                             *pDst,
                             Ch,
                             Cmd[80],          /* allocate a huge command buff
                         *bin_to_dec();
             int                   Len;
         structModemOptions sModemOptions;                /* modem options */

         get_modem_options(&sModemOptions);
             pDst = Cmd;
             *pDst++ = 'A';          /* get Modem's attention */
             *pDst++ = 'T';
             *pDst++ = 'S';          /* set R6 for wait for dial tone */
             *pDst++ = '6';
             *pDst++ = '=';
             pDst = bin_to_dec(pDst, sModemOptions.iWaitTone);
             *pDst++ = B_PAUSE;
             *pDst++ = 'S';          /* set R7 for wait for carrier time */
             *pDst++ = '7';
             *pDst++ = '=';
             pDst = bin_to_dec(pDst, sModemOptions.iWaitCarrier);
             *pDst++ = B_PAUSE;
             *pDst++ = 'D';
             *pDst++ = (sModemOptions.chDialType == PULSE) ? 'P' : 'T';
             pSrc = sModemOptions.pPhoneNumber;
             while (Ch = *pSrc++)
           if ((Ch >= '0' && Ch <= '9') || (Ch == ','))
             *pDst++ = Ch;
             *pDst++ = 0x0d;
             *pDst = 0;

             /* copy the command string into caller's buffer */
             pSrc = Cmd;
             pDst = pBuf;
             for (Len = 0; Len <= BufLen; ++Len)
           if (*pSrc==0)
             break;
           else
             *pDst++ = *pSrc++;
             return(Len);
 }




 /***        bin_to_dec - convert a binary number to ASCII decimal
  *
  *        bin_to_dec(pDst, i)
  *
  *        ENTRY
  *                pDst -> buffer to place the ASCII decimal
  *                i = integer to be converted to ASCII decimal
  *
  *        EXIT
  *                ASCII decimal placed in the buffer pointed by pDst
  *                bin_to_dec -> byte next to ASCII decimal in the buffer
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 char *bin_to_dec(pDst, i)
 char         *pDst;
 int          i;
 {
             if (i > 10)
           pDst = bin_to_dec (pDst, i/10);
             *pDst++ = '0' + (i % 10);
             return(pDst);
 }




 /***        get_modem_reply - get reply from modem
  *
  *        get_modem_reply()
  *
  *        ENTRY
  *
  *        EXIT
  *                get_modem_reply = MS_CONNECT MS_CONNECT1200 MS_NOCARRIER
  *                                  MS_NODIALTONE MS_NOREPLY MS_ERROR MS_BUSY
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 int get_modem_reply()
 {
             char                 Ch;
         USHORT                Cnt;
         int                Result,
                              RetCode;

             do                  /* skip CR, LF and get resultcode */
           if ((RetCode = DosRead(FileHndl, &Ch, 1, &Cnt)) != 0)
             Error(ERR_DOSREAD, RetCode);
         while ((Cnt == 1) && ((Ch == 0x0d) || (Ch == 0x0a)));

             switch(Cnt) {
           case 0:  Result = MS_NOREPLY;
                        break;
           case 1:  if ((Ch >= '0') && (Ch <= '7'))
                      Result = Ch - '0';
                    else
                      Result = MS_ERROR;
                    break;
           default: Result = MS_ERROR;
                    break;
         }

             return(Result);
 }




 /***        discon_modem - disconnect the modem
  *
  *        discon_modem()
  *
  *        ENTRY
  *
  *        EXIT
  *                modem disconnected
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void discon_modem()
 {
             /* Wait for the last command to clear, then be quiet for 1.5 seco
         DosSleep(1000L);                /* wait 1. second */
             flush_ques(FLUSHOUTPUT);        /* flush the transmit que */
         DosSleep(1500L);                /* wait 1.5 seconds */

             send_modem_cmd(MC_ESC);            /* put modem in command mode *
         DosSleep(2000L);                /* wait 2 seconds */

             send_modem_cmd(MC_ONHOOK);             /* place phone back on hoo
         DosSleep(200L);           /* wait for command to clear*/
 }




 /***        com_clr_flush - clear COM error and flush transmit/receive queues
  *
  *        com_clr_flush()
  *
  *        ENTRY
  *
  *        EXIT
  *                com error cleared
  *                transmit and receive queues of com port flushed out
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void com_clr_flush()
 {
         /* retrieve and clear the com error information */
         clr_com_error();
         /* flush transmit and receive queues */
         flush_ques(FLUSHINPUT);
         flush_ques(FLUSHOUTPUT);
 }




 /***        flush_ques - flush COM transmit/receive queue
  *
  *        flush_ques(FuncId)
  *
  *        ENTRY
  *                FuncId - set to one of: FLUSHINPUT FLUSHOUTPUT
  *
  *        EXIT
  *                transmit or receive queue of com port flushed
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void flush_ques(FuncId)
 USHORT        FuncId;
 {
         char        FlushData,        /* data returned by flush IOCTL functio
                 FlushParm = FLUSH_CMDINFO; /* param to flush IOCTL function *
         int        RetCode;

         /* flush transmit/receive queue */
         if ((RetCode = DosDevIOCtl(&FlushData, &FlushParm,
                                    FuncId, GENERIC, FileHndl)) != 0)
           Error(ERR_IOCTLFLUSHQUE, RetCode);
 }




 /***        clr_com_error - retrieve and clear COM error information
  *
  *        clr_com_error()
  *
  *        ENTRY
  *
  *        EXIT
  *                com error cleared
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void clr_com_error()

 {
         int          ComError,
                 RetCode;

         if ((RetCode = DosDevIOCtl(&ComError, 0L, GETCOMERROR,
                                    SERIAL, FileHndl)) != 0)
           Error(ERR_IOCTLGETCOMERROR, RetCode);
 }


 MONITOR.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MONITOR\MONITOR.C

 /****************************** Module Header ******************************\
 * Module Name: monitor.c
 *
 * This is a small monitoring test program.  It dumps monitor characters to
 * a file called monitor.txt.
 *
 * Created:      sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #define INCL_WIN
 #define INCL_DOSFILEMGR
 #define INCL_DOSNLS
 #include <os2.h>
 #include "ddeml.h"
 #include "monitor.h"
 #include "res.h"


 /*********** declares *********/


 int        APIENTRY DebugOutput( PCH );        /* private import */

 int lstrlen(PSZ psz);
 HDMGDATA EXPENTRY dataxfer(HCONV hConv, HSZ hszTopic, HSZ hszItem,
         USHORT usFmt, USHORT usType, HDMGDATA hDmgData);


 /************* GLOBAL VARIABLES  ************/

 HAB hab;
 HMQ hmq;
 HFILE hfile;
 HWND hwndFrame;
 HWND hwndClient;
 USHORT fsOutput = 0;

 void cdecl main(argc, argv)
 int argc;
 char **argv;
 {
     USHORT err;
     QMSG qmsg;
     ULONG ctlData;
     argc; argv;

     if (argc > 1) {
         DdeDebugBreak();
     }

     hab = WinInitialize(0);

     hmq = WinCreateMsgQueue(hab, 0);

     if (!InitTestSubs())
         DosExit(1, 0);

     if (!WinRegisterClass(hab, "Monitor Class", MonWndProc, 0L, 4L))
         DosExit(1, 0);

     ctlData = FCF_STANDARD;
     hwndFrame = WinCreateStdWindow(HWND_DESKTOP, 0L, &ctlData, "Monitor Class
             (PSZ)"DDE Monitoring window", (ULONG)WS_VISIBLE, (HMODULE)NULL, I
     WinSetWindowPos(hwndFrame, HWND_TOP, 0, 0, 0, 0, SWP_MINIMIZE | SWP_SHOW)

     if (err = DdeInitialize((PFNCALLBACK)dataxfer, DMGCMD_MONITOR, 0L)) {
         DdePostError(err);
         goto abort;
     }

     while (WinGetMsg(hab, &qmsg, 0, 0, 0)) {
         WinDispatchMsg(hab, &qmsg);
     }

     DdeUninitialize();

 abort:
     DosClose(hfile);
     WinDestroyWindow(hwndFrame);
     WinTerminate(hab);
     DosExit(TRUE, 0);
 }



 MRESULT EXPENTRY MonWndProc(
 HWND hwnd,
 USHORT msg,
 MPARAM mp1,
 MPARAM mp2)
 {
     USHORT usAction;

     switch (msg) {
     case WM_INITMENU:
         switch (SHORT1FROMMP(mp1)) {
         case IDSM_OUTPUT:
             WinSendMsg((HWND)mp2, MM_SETITEMATTR, MPFROM2SHORT(IDM_OUTPUT_FIL
                     MPFROM2SHORT(MIA_CHECKED,
                     fsOutput & (IDM_OUTPUT_FILE - IDSM_OUTPUT) ?
                     MIA_CHECKED : NULL));
             WinSendMsg((HWND)mp2, MM_SETITEMATTR, MPFROM2SHORT(IDM_OUTPUT_DEB
                     MPFROM2SHORT(MIA_CHECKED,
                     fsOutput & (IDM_OUTPUT_DEBUG - IDSM_OUTPUT) ?
                     MIA_CHECKED : NULL));
             WinSendMsg((HWND)mp2, MM_SETITEMATTR, MPFROM2SHORT(IDM_OUTPUT_SCR
                     MPFROM2SHORT(MIA_CHECKED,
                     fsOutput & (IDM_OUTPUT_SCREEN - IDSM_OUTPUT) ?
                     MIA_CHECKED : NULL));
             break;
         }
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDM_OUTPUT_FILE:
             if (fsOutput & (IDM_OUTPUT_FILE - IDSM_OUTPUT)) {
                 DosClose(hfile);
             } else {
                 if (DosOpen("monitor.txt", &hfile, &usAction, 0L, FILE_NORMAL
                         FILE_TRUNCATE | FILE_CREATE,
                         OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYNONE |
                         OPEN_FLAGS_WRITE_THROUGH | OPEN_FLAGS_SEQUENTIAL, 0L)
                     WinAlarm(HWND_DESKTOP, WA_ERROR);
                     return 0L;
                 }
             }
         /* fall through */
         case IDM_OUTPUT_DEBUG:
         case IDM_OUTPUT_SCREEN:
             fsOutput ^= LOUSHORT(mp1) - IDSM_OUTPUT;
             break;
         }
         break;

     case WM_CREATE:
         hwndClient = hwnd;
         /* fall through */
     default:
         return(StrWndProc(hwnd, msg, mp1, mp2));
         break;
     }

     return 0L;
 }




 HDMGDATA EXPENTRY dataxfer(hConv, hszTopic, hszItem, usFmt, usType,
         hDmgData)
 HCONV hConv;
 HSZ hszTopic;
 HSZ hszItem;
 USHORT usFmt;
 USHORT usType;
 HDMGDATA hDmgData;
 {
     char sz[MAX_MONITORSTR + 1];
     USHORT usWritten;

     hConv; hszTopic; hszItem; usFmt; usType;

     if (usType == XTYP_MONITOR) {
         DdeGetData(hDmgData, (PBYTE)sz, (ULONG)(MAX_MONITORSTR + 1), 0L);
         if (fsOutput & (IDM_OUTPUT_FILE - IDSM_OUTPUT))
             DosWrite(hfile, sz, lstrlen(sz), &usWritten);
         if (fsOutput & (IDM_OUTPUT_DEBUG - IDSM_OUTPUT))
             DebugOutput(sz);
         if (fsOutput & (IDM_OUTPUT_SCREEN - IDSM_OUTPUT))
             DrawString(hwndClient, sz);
         return(TRUE);
     }
     return(0);
 }


 int lstrlen(psz)
 PSZ psz;
 {
     int c = 0;

     while (*psz != 0) {
         psz++;
         c++;
     }
     return(++c);
 }


 MSGBOX.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\MSGBOX\MSGBOX.C

 /*      msgbox.c  - Message box control sample application        */
 /*      Created by Microsoft Corp., 1989                        */
 /*                                                              */
 #define INCL_WIN
 #include <os2.h>
 #include "msgbox.h"

 /* Procedure prototypes */
 MPARAM EXPENTRY MsgBoxDlgProc(HWND, USHORT, MPARAM, MPARAM);
 void cdecl main(void);


 /* Main routine -- creates a dialog box window */
 void cdecl main(void)
 {
     HAB habMsgBox;
     HMQ hmqMsgBox;

     /* Initialize Anchor Block, Message Queue */
     habMsgBox = WinInitialize(0);
     hmqMsgBox = WinCreateMsgQueue(habMsgBox, 0);

     /* Summon the dialog box */
     WinDlgBox(HWND_DESKTOP, NULL, MsgBoxDlgProc, (HMODULE) NULL, IDD_MSGBOX,

     /* Clean up */
     WinDestroyMsgQueue(hmqMsgBox);
     WinTerminate(habMsgBox);
 }

 /* Message box control routine */
 MRESULT EXPENTRY MsgBoxDlgProc(hWnd, msg, mp1, mp2)
 HWND hWnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     USHORT flStyle;
     SHORT  rc;

     switch (msg) {
     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case DID_OK:
             WinDismissDlg(hWnd, TRUE);
             break;

         case IDD_SHOWBOX: /* Show Box */
             /* Buttons? */
             rc = SHORT1FROMMR( WinSendDlgItemMsg(hWnd, IDD_OK0,
                     BM_QUERYCHECKINDEX, 0L, 0L));
             flStyle = (rc > 0) ? (USHORT) rc : 0;

             /* Icon style? */
             rc = SHORT1FROMMR( WinSendDlgItemMsg(hWnd, IDD_ICON0,
                     BM_QUERYCHECKINDEX, 0L, 0L));
             if (rc > 0) flStyle = (flStyle & 0xff0f) | ((USHORT) rc << 4);

             /* Default style? */
             rc = SHORT1FROMMR( WinSendDlgItemMsg(hWnd, IDD_DEF0,
                     BM_QUERYCHECKINDEX, 0L, 0L));
             if (rc > 0) flStyle = (flStyle & 0xf0ff) | ((USHORT) rc << 8);

             /* Get modality */
             if (WinSendDlgItemMsg(hWnd, IDD_SYSTEMMODAL, BM_QUERYCHECK, 0L, 0
                 flStyle |= MB_SYSTEMMODAL;

             /* Get help button attribute */
             if (WinSendDlgItemMsg(hWnd, IDD_HELP, BM_QUERYCHECK, 0L, 0L))
                 flStyle |= MB_HELP;

             /* Display the Message Box Type */
             WinSetDlgItemShort(hWnd, IDD_MSGBOXSTYLE, flStyle, FALSE);

             /* Pop up the message box */
             rc = WinMessageBox(HWND_DESKTOP, hWnd,
                     (PSZ)"Message Box Text Body\n(can contain several lines)"
                     (PSZ)"This is the title bar", 1, flStyle);

             /* Update the return code box */
             WinSetDlgItemShort(hWnd, IDD_RETURNCODE, rc, FALSE);
             break;

         default:
             break;
         }
         break;

     case WM_INITDLG: /* Push these three buttons */
         WinSendDlgItemMsg(hWnd,  IDD_OK0, BM_SETCHECK, (MPARAM) TRUE, 0L);
         WinSendDlgItemMsg(hWnd,IDD_ICON0, BM_SETCHECK, (MPARAM) TRUE, 0L);
         WinSendDlgItemMsg(hWnd, IDD_DEF0, BM_SETCHECK, (MPARAM) TRUE, 0L);
         break;

     default:
         return(WinDefDlgProc(hWnd, msg, mp1, mp2));
         break;
     }
     return 0L;
 }


 MSNGR.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MSNGR\MSNGR.C

 /****************************** MODULE Header ******************************\
 * Module Name:  msngr.c - Messenger application
 *
 * Created:  1/1/89  sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/

 #include "msngr.h"
 #include "string.h"

 /************* FILE GLOBALS  ************/

 char szMessengerClass[] = SZAPPCLASS;
 char szTitlePrefix[] = SZAPPTITLEPREFIX;
 char szTitle[MAX_TITLESTR + 1]="";
 char szEmailName[MAX_NAMESTR + 1] = "";

 HCONVLIST hConvUserList = 0;    /* holds all client conversations */
 HAB  hab;
 HMQ  hmq;
 HWND hwndMsngrFrame;
 HWND hwndMsngr;
 HWND hwndHolder;
 HWND hwndLB;
 HHEAP hheap;
 HPOINTER hptrLink = 0;
 PFNWP lpfnSysEFWndProc;         /* holds the system edit control proc */
 NPUSERLIST gnpUL = 0;   /* relates hConvs to hszs. */
 SHORT cyText;
 ULONG ulTimeout = 500;

 /*
  * The following arrays define which interface functions to use for a given
  * topic and item.
  * Hszize() fills in the hsz portions of this array.
  * Note: the order of some of these must corespond to the the index constants
  *        in msngr.h.
  */

 HSZ hszEmailName = 0;
 HSZ hszAppName = 0;

 /*                               HSZ    PROCEDURE       PSZ        */

 ITEMLIST sysTopicItemList[] = {
                                 { 0, TopicListXfer,  SZDDESYS_ITEM_TOPICS   }
                                 { 0, ItemListXfer,   SZDDESYS_ITEM_SYSITEMS }
                                 { 0, NotSupported,   SZDDESYS_ITEM_RTNMSG   }
                                 { 0, NotSupported,   SZDDESYS_ITEM_STATUS   }
                                 { 0, sysFormatsXfer, SZDDESYS_ITEM_FORMATS  }
                               };

 ITEMLIST msgTopicItemList[] = {
                                 { 0, msgUserNameXfer, SZUSERNAME},
                                 { 0, msgMessageXfer, SZMESSAGEDATA},
                                 { 0, bmpXfer, SZBMAPDATA},
                               };


 /*                           HSZ   PROCEDURE       #ofITEMS     PSZ     */

 TOPICLIST topicList[] = {
     /* The system topic is always assumed to be first. */
                             { 0, sysTopicItemList, IIL_SYSLAST, SZDDESYS_TOPI
                             { 0, msgTopicItemList, IIL_MSGLAST, SZDDEMSGTOPIC
                         };


 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   Main dude.
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 void cdecl main(argc, argv)
 int argc;
 char *argv[];
 {
     USHORT err;
     HPS hps;
     QMSG qmsg;
     ULONG createFlags;

     /*
      * check command line args
      *  User can specify a email name up to MAX_NAMESTR
      *  ! turns on a debug break
      */
     if (argc > 1) {
         if (strlen(argv[1]) > MAX_NAMESTR)
             NotifyUser(SZBADCOMPARAMS);
         else if (*argv[1] != '!')
             strcpy(szEmailName, argv[1]);
     }

     hab = WinInitialize(0);
     hheap = WinCreateHeap(0, 0, 0, 0, 0, HM_MOVEABLE | HM_VALIDSIZE);
     hmq = WinCreateMsgQueue(hab, 100);  /* Note the larger than normal Q ! */

     WinRegisterClass(hab, SZHOLDINGCLASS, WinDefWindowProc, 0L, 0L);
     WinRegisterClass(hab, SZAPPCLASS, MainWndProc, CS_SIZEREDRAW, 4L);

     /*
      * set up cyText for later
      */
     {
         FONTMETRICS fm;

         hps = WinGetPS(HWND_DESKTOP);
         GpiQueryFontMetrics(hps, (LONG)sizeof(FONTMETRICS), &fm);
         WinReleasePS(hps);
         cyText = (SHORT)fm.lMaxBaselineExt;
     }

     InitBmapModule();

     /*
      * force the user to set up his email name.
      */
     if (szEmailName[0] == '\0')
         WinDlgBox(HWND_DESKTOP, NULL, (PFNWP)GetNameDlgProc, (HMODULE)NULL,
                 IDD_GETNAME, (PVOID)szEmailName);

     /*
      * Initialize the DDE manager
      */
     if ((err = DdeInitialize((PFNCALLBACK)Callback, 0L, 0L)) != NULL) {
         DdePostError(err);
         goto abort;
     }

     /*
      * Create all HSZs we expect to need
      */
     Hszize();

     /*
      * Create our main window
      */
     createFlags = FCF_STANDARD;
     hwndMsngrFrame = WinCreateStdWindow(HWND_DESKTOP, 0L, NULL,
         SZAPPCLASS, "", 0L, (HMODULE)NULL, IDR_MSNGR1, &hwndMsngr);

     WinSetWindowPos(hwndMsngrFrame, HWND_TOP, 0, 0,
         250, 150, SWP_SHOW | SWP_ZORDER | SWP_SIZE);

     /*
      * set up the initial title string.
      */
     SetEmailName(szEmailName);

     /*
      * process the main window till dismissed.
      */
     while (WinGetMsg(hab, &qmsg, NULL, 0, 0))
         WinDispatchMsg(hab, &qmsg);

 abort:
     if (hptrLink != 0)
         WinDestroyPointer(hptrLink);

     WinDestroyWindow(hwndMsngrFrame);
     CloseBmapModule();
     DdeAppNameServer(hszAppName, ANS_UNREGISTER | ANS_FILTERON);
     UnHszize();
     DdeUninitialize();
     WinTerminate(hab);
     DosExit(EXIT_PROCESS, 0);
 }




 /********** Msngr Window Procedure **************/

 MRESULT EXPENTRY MainWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     char szCaption[MAX_CAPTIONSTR + 1];
     NPUSERLIST pUserItem;

     switch (msg) {
     case WM_CREATE:
         /*
          * initialize globals
          */
         hwndMsngr = hwnd;
         hwndLB = WinCreateWindow(hwndMsngr, WC_LISTBOX, "",
                 WS_VISIBLE | LS_NOADJUSTPOS, 0, 0, 0, 0, hwndMsngr,
                 HWND_TOP, WID_LISTBOX, NULL, NULL);

         /*
          * create a holding window to be the owner of all the floating
          * dialogs which need to be destroyed on exit.
          */
         hwndHolder = WinCreateWindow(HWND_OBJECT, SZHOLDINGCLASS, "", 0L,
                 0, -1, 0, 0, (HWND)NULL, HWND_TOP, 0, (PVOID)NULL, (PVOID)NUL
         /*
          * we register our name here so keeping track of who is out there
          * is simplified.  The DLL will now let us know when other
          * instances of this app come and go.
          *
          * As the DLL lets other msngr apps know we are here, they
          * will connect with us, ask us our name, and poke to us their
          * names.  Each poke will cause their name to be added to our
          * user list.
          *
          * If our requested name happens to be the same as one of theirs,
          * they will ignore our registration and assume we will change
          * our name.
          *
          * If we find that a poke from them is the same as our name, we
          * know that we are a duplicate.  We will then alter the email
          * name to be unique and reregister ourselves with the AppNameServer.
          * This will restart the registration notification process as
          * described here.
          */
         DdeAppNameServer(hszAppName, ANS_REGISTER | ANS_FILTERON);
         break;

     case WM_SIZE:
         WinSetWindowPos(hwndLB, HWND_TOP, -1, -1, SHORT1FROMMP(mp2) + 2,
                 SHORT2FROMMP(mp2) + 2, SWP_SIZE | SWP_MOVE | SWP_SHOW);
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDM_DOSENDTEXT:
             if (!(pUserItem = AccessSelection()))
                 break;
             WinDlgBox(HWND_DESKTOP, hwndMsngr, SendTextDlgProc, 0L,
                     IDD_SENDTEXT, (PVOID)pUserItem);
             break;

         case IDM_DOSENDBITMAP:
             if (!(pUserItem = AccessSelection()))
                 break;
             WinDlgBox(HWND_DESKTOP, hwndMsngr, SendBitmapDlgProc,
                     0L, IDD_SENDBITMAP, (PVOID)pUserItem);
             break;

         case IDM_DOSENDSONG:
             if (!(pUserItem = AccessSelection()))
                 break;
             WinDlgBox(HWND_DESKTOP, hwndMsngr, SendSongDlgProc, 0L,
                     IDD_SENDSONG, (PVOID)pUserItem);
             break;

         case IDM_INITLINK:
             if (!(pUserItem = AccessSelection()))
                 break;
             if (!WinIsWindow(hab, pUserItem->hwndLink)) {
                 CreateLinkWindow(pUserItem, LNKST_USERSTART);
             } else {
                 WinSetWindowPos(pUserItem->hwndLink, HWND_TOP,
                         0, 0, 0, 0,
                         SWP_SHOW | SWP_RESTORE | SWP_FOCUSACTIVATE);
             }
             break;
         }
         break;

     /*
      * we use this message so we can do asynchronous incomming message
      * notification.  This prevents us from eternal loops via the
      * callback function.
      */
     case UM_MSG_NOTIFY:
         lstrcat((PSZ)szCaption, (PSZ)SZMSGIN,
                 (PSZ)&(((PSZ)mp1)[lstrlen((PSZ)mp1) + 1]));
         NotifyUser2(szCaption, (PSZ)mp1);
         WinFreeMem(hheap, (NPBYTE)(USHORT)mp1,
                 MAX_MSGSTR + 1 + MAX_NAMESTR + 1);
         break;

     case WM_CLOSE:
         /*
          * destroy the holder window, and with it, shutdown any floating
          * dialogs.
          */
         WinDestroyWindow(hwndHolder);
         /* fall through */
     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }




 /***************************** Private Function ****************************\
 * DESCRIPTION:
 * This dialog forces the user to enter a string.  It may be used for
 * entering the user's initial email name or it can be used to change
 * the current email name.
 *
 * It must be invoked with a psz specified for its cData which is where its
 * result will be stored.  The psz can be no larger than MAX_NAMESTR.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 MRESULT EXPENTRY GetNameDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     PSZ pszName;

     switch (msg) {
     case WM_INITDLG: {
             HWND hwndEF;

             /*
              * store pszName into window words for later access and set up th
              * default name entry.
              * This assumes mp2=pszText for this dialog's entryfield.
              */
             WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
             if (*(PSZ)mp2 != '\0') {
                 /*
                  * we are changing a name because of a duplicate, change
                  * the title apropriately.
                  */
                 WinSetDlgItemText(hwnd, FID_TITLEBAR, SZDUPTITLE);
             }
             /*
              * set up our entryfield to be enhanced.
              */
             hwndEF = WinWindowFromID(hwnd, IDC_EF_NAME);
             lpfnSysEFWndProc = WinSubclassWindow(hwndEF, EnhancedEFWndProc);

             WinSetWindowText(hwndEF, (PSZ)mp2);
             WinSendMsg(hwndEF, EM_SETTEXTLIMIT, MPFROMSHORT(MAX_NAMESTR), 0L)
             return(0L);
         }
         break;

     case ENHAN_ENTER:
         /*
          * when the user hits the enter key, it will be passed from the
          * entryfield to here and we will use it as a signal to exit.
          */
         pszName = (PSZ)WinQueryWindowULong(hwnd, QWL_USER);
         WinQueryDlgItemText(hwnd, IDC_EF_NAME, MAX_NAMESTR + 1,
                 pszName);
         if (*pszName != '\0') {
             WinDismissDlg(hwnd, 0);
         }
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }




 /****************************************************************************
 *ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
 *³This window proc, which is actually a subclass of the standard entryfield ³
 *³type of window, notifies its owner of more interesting things than the    ³
 *³origonal did.                                                             ³
 *ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
 *****************************************************************************
 MRESULT EXPENTRY EnhancedEFWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (msg) {
     case WM_CHAR:
         if (LOUSHORT(mp1) & KC_SCANCODE &&
                 LOUSHORT(mp1) & KC_KEYUP &&
                 /*---HACK ALERT!---*/
                 LOBYTE(LOUSHORT(mp2)) == 0x0d) {
             NOTIFYOWNER(hwnd, ENHAN_ENTER,
                     (MPARAM)WinQueryWindowUShort(hwnd, QWS_ID), 0L);
         }
         break;
     }
     return(lpfnSysEFWndProc(hwnd, msg, mp1, mp2));
 }





 MRESULT EXPENTRY SendTextDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (msg) {
     case WM_INITDLG:
         /*
          * mp2 should be set to the pUserItem of the person we are sending to
          */
         WinSendDlgItemMsg(hwnd, IDC_EF_TEXT, EM_SETTEXTLIMIT,
                 MPFROMSHORT(MAX_MSGSTR + 1), 0L);

         if (((NPUSERLIST)(SHORT)mp2)->hConvMsg == 0) {
             NotifyUser(SZCANTCONNECT);
             WinDismissDlg(hwnd, 0);
             return(0);
         }
         WinSetWindowULong(hwnd, QWL_USER, (ULONG)((NPUSERLIST)(SHORT)mp2)->hC
         return(0);
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
         case IDC_SENDTEXT:
             {
                 char szMsg[MAX_MSGSTR + 1 + MAX_NAMESTR + 1];

                 WinQueryDlgItemText(hwnd, IDC_EF_TEXT, MAX_MSGSTR + 1,
                         (PSZ)szMsg);
                 if (szMsg[0] == '\0') {
                     NotifyUser(SZEMPTYMSG);
                     return(0);
                 }
                 lstrpak((PSZ)szMsg, (PSZ)szMsg, (PSZ)szEmailName);
                 if (!((USHORT)DdeClientXfer((PBYTE)szMsg,
                         (ULONG)(lstrlen((PSZ)szMsg) + lstrlen((PSZ)szEmailNam
                         (HCONV)WinQueryWindowULong(hwnd, QWL_USER),
                         msgTopicItemList[IIL_MSGXFER].hszItem, DDEFMT_TEXT,
                         XTYP_POKE, ulTimeout, NULL) & DDE_FACK)) {
                     NotifyUser(SZSENDFAILED);
                     MyPostError(DdeGetLastError());
                     return(0);
                 }
             }
             WinDismissDlg(hwnd, 0);
             break;

         default:
             /*
              * This would handle the DID_OK exit.
              */
             return(WinDefDlgProc(hwnd, msg, mp1, mp2));
             break;
         }
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }





 MRESULT EXPENTRY SendSongDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     switch (msg) {
     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0);
 }





 /***************************** Public  Function ****************************\
 *
 * This function is called by the DDE manager DLL and passes control onto
 * the apropriate function pointed to by the global topic and item arrays.
 * It handles all DDE interaction generated by external events.
 *
 * History:  1/1/89  created     sanfords
 \***************************************************************************/
 HDMGDATA EXPENTRY Callback(hConv, hszTopic, hszItem, usFmt, usType,
         hDmgData)
 HCONV hConv;
 HSZ hszTopic;
 HSZ hszItem;
 USHORT usFmt;
 USHORT usType;
 HDMGDATA hDmgData;
 {
     SHORT i, j;
     register ITEMLIST *pItemList;
     USHORT iItemLast;
     HDMGDATA hDmgDataRet;

     UNUSED usFmt;
     UNUSED hConv;

     if (usType == XTYP_REGISTER || usType == XTYP_UNREGISTER) {
         if (hszItem == hszAppName)
             /*
              * we are being notified that another msngr app is starting up
              * or going away.
              * hDmgData should be the App handle to use for initiateing.
              */
             if (usType == XTYP_REGISTER) {
                 RegisterUser(hDmgData, NULL, TRUE);
             } else {
                 UnregisterUser(hDmgData);
             }
         return(0);
     }


     if (usType == XTYP_WILDINIT) {
         /*
          * He wants a hsz list of all our available app/topic pairs
          * that convorm to hszTopic and hszItem(App).
          */
         HSZ ahsz[(ITL_LAST + 3) * 2];

         if (hszItem != hszAppName && hszItem != NULL)
             return(0);

         j = 0;
         for (i = 0; i <= ITL_LAST; i++) {
             if (hszTopic == NULL || hszTopic == topicList[i].hszTopic) {
                 ahsz[j++] = hszAppName;
                 ahsz[j++] = topicList[i].hszTopic;
             }
         }

         if (hszTopic == NULL || hszTopic == hszEmailName) {
             ahsz[j++] = hszAppName;
             ahsz[j++] = hszEmailName;
         }

         ahsz[j++] = ahsz[j++] = 0L;
         return(DdePutData((PBYTE)&ahsz[0], (ULONG)sizeof(HSZ) * j, 0L,
                 (HSZ)0L, 0, 0));
     }


     /*
      * Check for Topic/Item convention for Linking.  Remember that ADVDATA
      * is client intended so its backwards.
      */
     if ((hszTopic == hszEmailName) ||
             (usType == XTYP_ADVDATA && hszItem == hszEmailName)) {
         return(LinkXfer((PXFERINFO)&hDmgData, hszTopic == hszEmailName ?
                 hszItem : hszTopic));
     }

     /*
      * Check our hsz tables and send to the apropriate proc.
      * We use DdeCmpHsz() so hsz comparisons are case insensitive.
      */
     for (i = 0; i <= ITL_LAST; i++) {
         if (DdeCmpHsz(topicList[i].hszTopic, hszTopic) == 0) {
             if (usType == XTYP_INIT) {
                 /*
                  * it must be a request to start another server conversation.
                  */
                 return(TRUE);
             }
             pItemList = topicList[i].pItemList;
             iItemLast = topicList[i].iItemLast;
             for (j = 0; j <= iItemLast; j++) {
                 if (DdeCmpHsz(pItemList[j].hszItem, hszItem) == 0) {
                     hDmgDataRet = (*pItemList[j].npfnCallback)
                             ((PXFERINFO)&hDmgData);
                     /*
                      * The table functions return a boolean or data.
                      * It gets translated here.
                      */
                     switch (usType & XCLASS_MASK) {
                     case XCLASS_DATA:
                         return(hDmgDataRet);
                         break;
                     case XCLASS_FLAGS:
                         return(hDmgDataRet ? DDE_FACK : DDE_NOTPROCESSED);
                         break;
                     case XCLASS_BOOL:
                         return(TRUE);
                     default:
                         return(0);
                         break;
                     }
                     break;
                 }
             }
             break;
         }
     }
     return(0);
 }





 /***************************** Private Function ****************************\
 * This passes out a standard tab-delimited list of topic names for this
 * application.
 *
 * This support is required for other apps to be able to
 * find out about us.  This kind of support should be in every DDE
 * application.
 *
 * History:  1/3/89  created     sanfords
 \***************************************************************************/
 HDMGDATA TopicListXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     USHORT cb, cbAlloc, iTopic, cch;
     PSZ pszTopicList;
     HDMGDATA hDmgData;
     char szT[MAX_NAMESTR + 1];
     SHORT lit;

     if (pXferInfo->usFmt != DDEFMT_TEXT ||
             (pXferInfo->usType != XTYP_REQUEST &&
             pXferInfo->usType != XTYP_ADVREQ))
         return(0);
     /*
      * construct the list of topics we have
      */
     cbAlloc = 0;
     for (iTopic = 0; iTopic <= ITL_LAST; iTopic++)
         cbAlloc += lstrlen(topicList[iTopic].pszTopic) + 1;
     lit = 0;
     while (WinSendMsg(hwndLB, LM_QUERYITEMTEXT,
             MPFROM2SHORT(lit, MAX_NAMESTR + 1), (MPARAM)szT)) {
         cbAlloc += lstrlen(szT) + 1;
         lit++;
     }
     pszTopicList = FarAllocMem(hheap, cbAlloc);
     if (!LOUSHORT(pszTopicList))
         return(0);
     cb = 0;
     for (iTopic = 0; iTopic <= ITL_LAST; iTopic++) {
         cch = lstrlen(topicList[iTopic].pszTopic) + 1;
         DdeCopyBlock(topicList[iTopic].pszTopic, pszTopicList + cb, (ULONG)cc
         cb += cch;
         pszTopicList[cb - 1] = '\t';
     }
     lit = 0;
     while (WinSendMsg(hwndLB, LM_QUERYITEMTEXT,
             MPFROM2SHORT(lit, MAX_NAMESTR + 1), (MPARAM)szT)) {
         cch = lstrlen(szT) + 1;
         DdeCopyBlock(szT, pszTopicList + cb, (ULONG)cch);
         cb += cch;
         pszTopicList[cb - 1] = '\t';
         lit++;
     }
     pszTopicList[cb - 1] = '\0';
     hDmgData = DdePutData(pszTopicList, (ULONG)cb, 0L, pXferInfo->hszItem,
             DDEFMT_TEXT, 0);
     WinFreeMem(hheap, (NPBYTE)(SHORT)pszTopicList, cbAlloc);
     return(hDmgData);
 }




 /***************************** Private Function ****************************\
 * This passes out a standard tab-delimited list of item names for the
 * specified topic.
 *
 * This support is required for other apps to be able to
 * find out about us.  This kind of support should be in every DDE
 * application.
 *
 * History:  1/3/89  created     sanfords
 \***************************************************************************/
 HDMGDATA ItemListXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     USHORT cb, cbAlloc, iTopic, iItem, iLast, cch;
     ITEMLIST *pItemList = 0;
     PSZ pszItemList;
     HDMGDATA hDmgData;

     if (pXferInfo->usFmt != DDEFMT_TEXT ||
                 (pXferInfo->usType != XTYP_REQUEST &&
                 pXferInfo->usType != XTYP_ADVREQ))
         return(0);
     /*
      * construct the list of items we support for this topic.
      */
     for (iTopic = 0; iTopic < ITL_LAST; iTopic++) {
         if (pXferInfo->hszTopic == topicList[iTopic].hszTopic) {
             pItemList = topicList[iTopic].pItemList;
             iLast = topicList[iTopic].iItemLast;
             break;
         }
     }
     if (pItemList == 0)
         return(0);
     cbAlloc = 0;
     for (iItem = 0; iItem < iLast; iItem++)
         cbAlloc += lstrlen(pItemList[iItem].pszItem) + 1;
     pszItemList = FarAllocMem(hheap, cbAlloc);
     if (!LOUSHORT(pszItemList))
         return(0);
     cb = 0;
     for (iItem = 0; iItem < iLast; iItem++) {
         cch = lstrlen(pItemList[iItem].pszItem) + 1;
         DdeCopyBlock(pItemList[iItem].pszItem, pszItemList + cb, (ULONG)cch);
         cb += cch;
         pszItemList[cb - 1] = '\t';
     }
     pszItemList[cb - 1] = '\0';
     hDmgData = DdePutData(pszItemList, (ULONG)cb, 0L,
             pXferInfo->hszItem, DDEFMT_TEXT, 0);
     WinFreeMem(hheap, (NPBYTE)(USHORT)pszItemList, cbAlloc);
     return(hDmgData);
 }




 /***************************** Private Function ****************************\
 * Used for unsupported transfers.
 *
 * History:  1/1/89  created     sanfords
 \***************************************************************************/
 HDMGDATA NotSupported(pXferInfo)
 PXFERINFO pXferInfo;
 {
     pXferInfo;

     return(0);
 }





 /***************************** Private Function ****************************\
 * Gives out a 0 terminated array of dde format numbers supported by this app.
 *
 * This support is required for other apps to be able to
 * find out about us.  This kind of support should be in every DDE
 * application.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 HDMGDATA sysFormatsXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     USHORT Formats[2];

     if (pXferInfo->usFmt != DDEFMT_TEXT ||
             (pXferInfo->usType != XTYP_REQUEST &&
             pXferInfo->usType != XTYP_ADVREQ))
         return(0);

     Formats[0] = DDEFMT_TEXT;
     Formats[1] = 0;
     return(DdePutData((PBYTE)Formats, 4L, 0L, pXferInfo->hszItem,
             DDEFMT_TEXT, 0));
 }



 /***************************** Private Function ****************************\
 * Gives out our szEmailName or registers someone elses.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 HDMGDATA msgUserNameXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     CONVINFO ci;

     if (pXferInfo->usFmt != DDEFMT_TEXT)
         return(0);

     switch (pXferInfo->usType) {
     case XTYP_REQUEST:
         /*
          * someone wants to know our name...cool.
          */
         return(DdePutData(szEmailName, (ULONG)(lstrlen(szEmailName) + 1),
                 0L, pXferInfo->hszItem, DDEFMT_TEXT, 0));
         break;

     case XTYP_POKE:
         /*
          * Someone is responding to our initial registration.  Add them
          * to our list.  If their name is the same as ours, WE are at
          * fault and must change our email name and reregister.
          */
         DdeQueryConvInfo(pXferInfo->hConv, &ci, QID_SYNC);
         RegisterUser(ci.hApp, pXferInfo->hDmgData, FALSE);
         return(1);
         break;
     }
     return(0);
 }





 /*
  * This is used for receiving text messages.
  */
 HDMGDATA msgMessageXfer(pXferInfo)
 PXFERINFO pXferInfo;
 {
     PSZ pszMsg;

     if (pXferInfo->usFmt != DDEFMT_TEXT)
         return(DDE_NOTPROCESSED);

     switch (pXferInfo->usType) {
     case XTYP_POKE:
         /*
          * we have the message text followed by the users name.
          */
         pszMsg = FarAllocMem(hheap, MAX_MSGSTR + 1 + MAX_NAMESTR + 1);
         DdeGetData(pXferInfo->hDmgData, (PBYTE)pszMsg,
                 (ULONG)(MAX_MSGSTR + 1 + MAX_NAMESTR + 1), 0L);
         DdeFreeData(pXferInfo->hDmgData);
         /*
          * We must do an assynchronous user notification so we don't
          * clog up the DLL in our callback.
          */
         WinPostMsg(hwndMsngr, UM_MSG_NOTIFY, pszMsg, 0L);
         return(1);
         break;

     default:
         break;
     }
     return(0);
 }





 /***************************** Private Function ****************************\
 *  This creates often used global hszs from standard global strings.
 *  It also fills the hsz fields of the topic and item tables.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 void Hszize()
 {
     register ITEMLIST *pItemList;
     USHORT iTopic, iItem;

     hszAppName = DdeGetHsz((PSZ)SZDDEAPPNAME, 0, 0);

     for (iTopic = 0; iTopic <= ITL_LAST; iTopic++) {
         topicList[iTopic].hszTopic = DdeGetHsz(topicList[iTopic].pszTopic, 0,
         pItemList = topicList[iTopic].pItemList;
         for (iItem = 0; iItem <= topicList[iTopic].iItemLast; iItem++) {
             pItemList[iItem].hszItem = DdeGetHsz(pItemList[iItem].pszItem, 0,
         }
     }
 }





 /***************************** Private Function ****************************\
 *  This destroys often used global hszs from standard global strings.
 *
 * History:      1/1/89  Created         sanfords
 \***************************************************************************/
 void UnHszize()
 {
     register ITEMLIST *pItemList;
     USHORT iTopic, iItem;

     DdeFreeHsz(hszAppName);
     DdeFreeHsz(hszEmailName);

     for (iTopic = 0; iTopic <= ITL_LAST; iTopic++) {
         DdeFreeHsz(topicList[iTopic].hszTopic);
         pItemList = topicList[iTopic].pItemList;
         for (iItem = 0; iItem <= topicList[iTopic].iItemLast; iItem++) {
             DdeFreeHsz(pItemList[iItem].hszItem);
         }
     }
 }







 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   This routine returns the userlist item pointed to by the listbox selectio
 *   It notifies the user of any errors and returns 0 in that case.
 *
 * History:      1/17/89     Created         sanfords
 \***************************************************************************/
 NPUSERLIST AccessSelection()
 {
     NPUSERLIST pUserItem;
     SHORT lit;

     lit = (SHORT)WinSendMsg(hwndLB, LM_QUERYSELECTION, 0L, 0L);
     if (lit == LIT_NONE) {
         NotifyUser(SZMAKESELECTION);
         return(0);
     }
     pUserItem = (NPUSERLIST)(SHORT)WinSendMsg(hwndLB, LM_QUERYITEMHANDLE,
             (MPARAM)lit, 0L);
     return(pUserItem);
 }



 /*
  * if fRegister : (We got the name via a request)
  * This routine creates a connection with hApp on the msgTopic and
  * gets the user's name from the connection.  It then looks for the
  * user's name in our current list.  If the name is already there
  * we fail - he is a fault, otherwise we add the user name to our
  * list of users. We then poke our name to the server we connected
  * with so he knows who we are.
  *
  * else :   (We got the name via a poke)
  * hData contains a valid user name which we will add.
  * If it is a dup of ours, we must change our name and reregister.  The
  * data handle is freed on return.
  */
 BOOL RegisterUser(
 HAPP hApp,
 HDMGDATA hData,
 BOOL fRegister)
 {
     HCONV hConv;
     HSZ hsz;
     BOOL fDup;

     hConv = DdeConnect(hszAppName, topicList[ITL_MSG].hszTopic, NULL, hApp);
     if (!hConv)  {
         return(FALSE);
     }

     if (fRegister)
         hData = DdeClientXfer(NULL, 0L, hConv,
                 msgTopicItemList[IIL_MSGUSERNAME].hszItem,
                 DDEFMT_TEXT, XTYP_REQUEST, DEFTIMEOUT, NULL);

     if (!hData)
         return(FALSE);

     hsz = DdeGetHsz(DdeAccessData(hData), 0, 0);
     DdeFreeData(hData);

     fDup = (hsz == hszEmailName) || FindUser(gnpUL, hsz);

     if (fDup && !fRegister) {
         /*
          * OOPS - we are a duplicate name! - Change our name and reregister!!
          * (we must first stop callbacks so the dialog modal loop
          *  doesn't allow more messy callbacks to come in.)
          */
         DdeEnableCallback(NULL, FALSE);
         while (gnpUL)
             DestroyUser(gnpUL);
         WinSendMsg(hwndLB, LM_DELETEALL, 0L, 0L);
         WinDlgBox(HWND_DESKTOP, NULL, (PFNWP)GetNameDlgProc, (HMODULE)NULL,
                 IDD_GETNAME, (PVOID)szEmailName);
         SetEmailName(szEmailName);
         DdeEnableCallback(NULL, TRUE);
         /*
          * reregister with the name server so others know we changed our name
          */
         DdeAppNameServer(hszAppName, ANS_UNREGISTER);
         DdeAppNameServer(hszAppName, ANS_REGISTER);
         DdeFreeHsz(hsz);
         DdeFreeData(hData);
         DdeDisconnect(hConv);
         return(FALSE);
     }

     if (!fDup)
         AddUser(hConv, hsz, hApp);
     /*
      * Now poke our name to him (if he is registering), so he gets updated
      * or discovers he's a duplicate.
      */
     if (fRegister) {
         DdeClientXfer(szEmailName, lstrlen(szEmailName) + 1L,
                 hConv, msgTopicItemList[IIL_MSGUSERNAME].hszItem,
                 DDEFMT_TEXT, XTYP_POKE, DEFTIMEOUT, NULL);
     }
     return(TRUE);
 }



 /*
  * We search for the hApp in our user list.  If its not there we fail.
  * Otherwise, we destroy any associated connections or windows and
  * remove the entry from our list.
  *
  * The case of duplicate name re-registration is covered here since
  * we ignore duplicate registrations and so we will not find hApp
  * if a duplicate name is being unregistered.
  */
 BOOL UnregisterUser(
 HAPP hApp)
 {
     NPUSERLIST npUser;
     SHORT lit;

     if (hApp == 0) {
         return(FALSE);
     }
     npUser = gnpUL;
     while (npUser) {
         if (hApp == npUser->hApp)
             break;
         npUser = npUser->next;
     }

     if (!npUser) {
         return(FALSE);
     }

     /*
      * Remove item from listbox.
      */
     if (lit = (SHORT)WinSendMsg(hwndLB, LM_QUERYITEMCOUNT, 0L, 0L)) {
         while (lit--) {
             if (npUser == (NPUSERLIST)(SHORT)WinSendMsg(hwndLB,
                     LM_QUERYITEMHANDLE, MPFROMSHORT(lit), 0L)) {
                 WinSendMsg(hwndLB, LM_DELETEITEM, MPFROMSHORT(lit), 0L);
                 break;
             }
         }
     }
     DestroyUser(npUser);
 }



 VOID SetEmailName(psz)
 PSZ psz;
 {
     if (hszEmailName)
         DdeFreeHsz(hszEmailName);
     hszEmailName = DdeGetHsz(psz, 0, 0);
     lstrcpy(szEmailName, psz);
     lstrcat(szTitle, szTitlePrefix, szEmailName);
     WinSetWindowText(hwndMsngrFrame, szTitle);
 }




 void NotifyUser(psz)
 PSZ psz;
 {
     WinMessageBox(HWND_DESKTOP, hwndMsngr, psz, SZNOTIFYCAPTION,
             WID_NOTIFY1,
             MB_OK | MB_DEFBUTTON1 | MB_ICONASTERISK | MB_MOVEABLE);
 }




 void NotifyUser2(pszCaption, pszText)
 PSZ pszCaption;
 PSZ pszText;
 {
     WinMessageBox(HWND_DESKTOP, hwndMsngr, pszText, pszCaption, WID_NOTIFY2,
             MB_OK | MB_DEFBUTTON1 | MB_ICONASTERISK | MB_MOVEABLE);
 }




 NEAD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\NEAD\NEAD.C

 /*************************************************************

  This program implements an extended attributes editor.
  Included in this file are all of the PM interface procedures.
  All of these procs represent procedures for dialog boxes.

  This program was written by Jeff Johnson, 7/89, with pieces
  of code pooled from already existing applications.

  This code is not fully implemented as it was written to show
  how to use EAs.  Specifically, the Copy, Copy all, and Paste
  buttons are not implemented, Icons and bitmaps are not
  supported, and Multi-value, single-type EAs are also not
  implemented.

  The EA code revolves around the HoldFEA structure.  This
  structure is a linked list which contains one EA per
  structure.  Each HoldFEA structure is dynamically allocated
  as are the two string pointer members of the structure,
  szName and aValue.  This is done to make handling the EAs
  easier and more flexable.

  Procedures in this file:
    main()          Sets up the PM environment and heap and
                    calls the main dialog procedure MainDlgProc
    MainDlgProc()   Handles the main window messages
    AddEAProc()     Handles new EA name entry and type selection
    AsciiEditProc() Handles editing of EA name/value
    IconDlgProc()   Unimplemented icon procedure handler
    MultiTypeProc() Handles the Multi-type dialog box
    ShowEAType()    Shows Selected EA Type for MainDlgProc
    EditEA()        Handles selection of an EA in the listbox
    GetCurFEA()     Gets a ptr to the hi-lited EA in the listbox

 **************************************************************/

 #include "nead.h"

 /********************* GLOBALS *******************************/

 CHAR szFileName[CCHMAXPATH];    /* Holds current EA file */
 CHAR szEAName[MAXEANAME+1];     /* Used to return the EA name +1 for NULL */
 USHORT usRetEAType;             /* Used to return selected EA Type */
 BOOL FILE_ISOPEN = FALSE;       /* File Open flag */
 BOOL FILE_CHANGED = FALSE;      /* File Changed flag */
 BOOL COMMAND_LINE_FILE = FALSE; /* Flag to determine if a file was on the CL
 HHEAP hhp;                      /* Pointer to the heap */
 CHAR *szAscii,*szScratch;       /* 2 Pointer used to return misc. strings */
 HOLDFEA *pHoldFEA;              /* Global EA linked-list pointer          */
 DELETELIST *pDelList;           /* Global ptr to l-l of deleted EAs       */
 EADATA ConvTable[EATABLESIZE] = {    /* Setup the table */
    { EA_LPBINARY  ,"Length preceeded hex binary\0        ",IDD_LPDATA},
    { EA_LPASCII   ,"Length preceeded ascii\0             ",IDD_LPDATA},
    { EA_ASCIIZ    ,"Asciiz\0                             ",IDD_ASCIIZ},
    { EA_LPBITMAP  ,"Length preceeded bitmap\0            ",IDD_LPDATA},
    { EA_LPMETAFILE,"Metafile\0                           ",IDD_LPDATA},
    { EA_LPICON    ,"Length preceeded icon\0              ",IDD_LPDATA},
    { EA_ASCIIZFN  ,"Asciiz file name of associated data\0",IDD_ASCIIZ},
    { EA_ASCIIZEA  ,"Asciiz EA of associated data\0       ",IDD_ASCIIZ},
    { EA_MVMT      ,"Multi value multi type field\0       ",IDD_MULTILIST},
    { EA_MVST      ,"Multi value single type field\0      ",IDD_MULTILIST},
    { EA_ASN1      ,"ASN.1 field\0                        ",IDD_ASCIIZ},
    { 0            ,"Non conventional format\0            ",IDD_ASCIIZ}
    };

 /*************************************************************/

 /*
  * Function name: main()
  *
  * Parameters:  argc, argv.  If the user places a file name on the command
  *              line, its EAs will be retrieved by default, otherwise
  *              the user will be prompted to select a file.
  *
  * Returns: Always returns 0
  *
  * Purpose: Parses the command line, sets up the PM environment, creates
  *          a global memory heap, calls the main dialog proc, then
  *          cleans up and exits.
  *
  * Usage/Warnings:
  *
  * Calls: ParseFileName, MainDlgProc (thru PM)
  */

 int main(int argc, char *argv[])
 {
    HAB          hab;
    HMQ          hmq;

    if(argc > 1)  /* If a command line file, get EAs from it */
       if(ParseFileName(szFileName,argv[1]) != FILE_VALID) /* Bad file name */
          *szFileName=0;
       else
          COMMAND_LINE_FILE = TRUE;

    /* Note:  The following 3 setup calls aren't currently checked for errors
    hab = WinInitialize(0);
    hmq = WinCreateMsgQueue(hab, 0);
    hhp = WinCreateHeap(0, 0, GROWSIZE, 0, 0,
                           HM_MOVEABLE | HM_VALIDSIZE | HM_VALIDFLAGS);

    WinDlgBox(HWND_DESKTOP, HWND_DESKTOP,
                          MainDlgProc,(HMODULE) NULL,IDD_MAIN,NULL);

    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);
    return 0;
 }


 /*
  * Function name: MainDlgProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              No user data is expected in the WM_INITDLG.
  *
  * Returns: Always returns 0, Exits with WinDismissDlg set to TRUE
  *
  * Purpose: Handles all the messages associated with the main window
  *          and calls the appropriate handling procedures.  The initialize
  *          routine sets up the program icon and posts a load file message
  *          to itself.
  *
  * Usage/Warnings: Called only by main()
  *
  * Calls:  OpenFile(), AddEA(), DeleteCurEA, WriteEAs, ShowEAType, EditEA
  */

 MRESULT EXPENTRY MainDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    HPOINTER hpointer;
    USHORT   usRet;

    switch(msg)
    {
       case WM_INITDLG:
          hpointer = WinLoadPointer(HWND_DESKTOP,            /* Set Nead Icon
                                    (HMODULE) NULL,IDR_EAD);
          WinPostMsg(hwnd,WM_SETICON,hpointer,0L);

          /* Get EAs from file on Command line, or prompt for file name */
          WinPostMsg(hwnd,WM_USER,NULL,NULL);
          return 0L;

       case WM_USER:  /* Posted by WM_INITDLG allows main window to open 1st *
          OpenFile(hwnd, COMMAND_LINE_FILE ? ARGFILE : 0);
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case IDD_NEWFILE:     /* Select another file to view/edit EAs */
                OpenFile(hwnd,0);
                return 0L;

             case IDD_ADD:         /* Add an EA to current list in memory  */
                AddEA(hwnd);
                return 0L;

             case IDD_DELETE:      /* Delete an EA from memory             */
                DeleteCurEA(hwnd);
                return 0L;

             case IDD_WRITE:       /* Write EA's out to disk               */
                WriteEAs(hwnd);
                return 0L;

             case IDD_EDIT:        /* Edit the hilited EA                  */
                EditEA(hwnd);
                return 0L;

             case IDD_COPY:        /* Copy and Paste aren't                */
             case IDD_PASTE:       /* currently implemented                */
                return 0L;

             case IDD_QUIT:
                if(FILE_CHANGED)   /* Give user a chance to save changes   */
                {
                   usRet=WinMessageBox(HWND_DESKTOP,hwnd,
                   "The current file has been changed.  Do you \
 wish to save the changes before proceeding?",
                   "Warning",0,MB_YESNOCANCEL | MB_ICONQUESTION);
                   switch(usRet)
                   {
                      case MBID_YES:    /* They want to save the EAs       */
                         WriteEAs(hwnd);
                         break;
                      case MBID_CANCEL: /* They don't really want to quit  */
                         return 0L;
                   }
                }
                WinDismissDlg(hwnd,TRUE);
                return 0L;
          }

       case WM_CONTROL:
          switch(SHORT1FROMMP(mp1))
          {
             case IDD_LBOX:
                switch(SHORT2FROMMP(mp1))
                {
                   case LN_SELECT:       /* A new item has been hi-lited */
                      ShowEAType(hwnd);  /* Display appropriate EA Type  */
                      return 0L;

                   case LN_ENTER:        /* Edit the selected EA         */
                      EditEA(hwnd);
                      return 0L;
                }
          }
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: AddEAProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              If user data is passed in, it indicates it is a m-m
  *              add and the EA Name is in ->Point and the user should
  *              not be allowed to edit it.
  *
  * Returns: Exits with WinDismissDlg set to TRUE if the user selects OK,
  *          FALSE if the user selects CANCEL.
  *
  * Purpose: Handles the screen that allows the user to enter a new EA Name
  *          and select the EA type.  It checks to make sure the name is
  *          unique and is not NULL before returning.  The user pointer is
  *          used to determine whether the name is allowed to change or not.
  *          If it points to a PassData structure, the Point member gives the
  *          static EA Name.
  *
  * Usage/Warnings: Called by both the add EA routine and the m-m EA add
  *                 routine.  This routine places the user typed name in
  *                 global szEAName, and the EA type in global usRetEAType.
  *
  * Calls:  EAExists()
  */

 MRESULT EXPENTRY AddEAProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    static CHAR bPDat = 0; /* flag indicating if the pPDat struct is valid */
    PASSDATA FAR *pPDat;   /* struct used to pass in default EA name       */

    switch(msg)
    {
       case WM_INITDLG:
          bPDat = 0;
          WinSendDlgItemMsg(hwnd, IDD_EANAME,EM_SETTEXTLIMIT,
                                  MPFROM2SHORT(MAXEANAME,0),NULL);
          WinSendDlgItemMsg(hwnd, EA_LPASCII,BM_CLICK,0L,0L);

          pPDat=PVOIDFROMMP(mp2);
          if(pPDat)
          {
             bPDat = 1;
             WinSetDlgItemText(hwnd, IDD_EANAME,pPDat->Point);
             WinSetDlgItemText(hwnd, IDD_TITLE,
                               "Add a Multi-type field to the EA");
             WinEnableWindow(WinWindowFromID(hwnd,IDD_EANAME),
                             FALSE);                /* Disable EA Name field *
             WinSetFocus(HWND_DESKTOP,WinWindowFromID(hwnd,DID_OK));

             return (MRESULT) TRUE;       /* We don't want default focus setti
          }
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:
                if(bPDat == 0)          /* It is a new EA (not multi-type)   *
                {
                   WinQueryDlgItemText(hwnd, IDD_EANAME, MAXEANAME+1, szEAName

                   if(!strlen(szEAName))    /* Don't allow a Null EA Name    *
                   {
                      WinAlarm(HWND_DESKTOP,WA_ERROR);
                      WinMessageBox(HWND_DESKTOP, hwnd,
                                    "Cannot create a NULL EA name.",
                                    NULL, 0, MB_OK);
                      return 0L;
                   }

                   if(EAExists(szEAName))   /* Don't allow duplicate EA Name *
                   {
                      WinAlarm(HWND_DESKTOP,WA_ERROR);
                      WinMessageBox(HWND_DESKTOP, hwnd,
                           "EA name already exists.  Can't create that name.",
                           NULL, 0, MB_OK);
                      return 0L;
                   }
                }
                usRetEAType = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, EA_ASCIIZ,
                                                    BM_QUERYCHECKINDEX, 0L, 0L
                WinDismissDlg(hwnd, TRUE);
                return 0L;

             case DID_CANCEL:
                WinDismissDlg(hwnd, FALSE);
                return 0L;
          }
          break;
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: AddEAProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              The user pointer passes in a PassData struct.  The Point
  *              member points to the default EA Name and the usIndex
  *              member determines whether or not the user can modify the
  *              EA Name.  The fFlag member determines the status of the
  *              Need/nice bit.  TRUE indicates the Need bit is set.  The
  *              default Value is passed in global szAscii.
  *
  * Returns: Exits with WinDismissDlg set to TRUE if the user selects OK,
  *          FALSE if the user selects CANCEL.  Global szEAName contains the
  *          modified EA Value.  the fFlag member of the passed in data will
  *          be updated to reflect the current state of the Need bit.
  *
  * Purpose: This proc handles the editing of EA Names and their associated
  *          ASCII strings.
  *
  * Usage/Warnings: A PassData struct must be passed in when called.
  *                 Note that the Value buffer is dynamically allocated and
  *                 it is the responsibility of the calling procedure to
  *                 deallocate the buffer (szScratch) when finished with it.
  *                 Note also that memory allocations are NOT fully error
  *                 trapped.
  *
  * Calls:  EAExists()
  */


 MRESULT EXPENTRY AsciiEditProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    static PASSDATA FAR *pPDat;

    switch(msg)
    {
       case WM_INITDLG:
          WinSendDlgItemMsg(hwnd, IDD_EANAME,EM_SETTEXTLIMIT,
                                  MPFROM2SHORT(MAXEANAME,0),NULL);
          WinSendDlgItemMsg(hwnd, IDD_EAVALUE,EM_SETTEXTLIMIT,
                                  MPFROM2SHORT(MAXEAVALUE,0),NULL);
          pPDat=PVOIDFROMMP(mp2);

          WinSetDlgItemText(hwnd, IDD_EANAME,pPDat->Point);
          WinSetDlgItemText(hwnd, IDD_EATYPE,ConvTable[usRetEAType].szFormat);
          WinSendDlgItemMsg(hwnd, IDD_NEEDBIT, BM_SETCHECK,
                            MPFROM2SHORT(pPDat->fFlag,0),NULL);

          if(szAscii)   /* Set default EA Value if global var is != NULL */
             WinSetDlgItemText(hwnd, IDD_EAVALUE,szAscii);

          if(pPDat->usIndex) /* Disable EANAME if passed in a non-zero value *
          {
             WinEnableWindow(WinWindowFromID(hwnd,IDD_EANAME),FALSE);
             WinEnableWindow(WinWindowFromID(hwnd,IDD_NEEDBIT),FALSE);
             WinSetFocus(HWND_DESKTOP,WinWindowFromID(hwnd,IDD_EAVALUE));
             return (MRESULT) TRUE;       /* We don't want default focus setti
          }
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:
                pPDat->fFlag = (BYTE) SHORT1FROMMR( WinSendDlgItemMsg(hwnd, ID
                                                        BM_QUERYCHECK,
                                                        0L, 0L) );
                WinQueryDlgItemText(hwnd, IDD_EANAME, MAXEANAME+1, szEAName);

                if(!strlen(szEAName))    /* They nulled out the name */
                {
                   WinAlarm(HWND_DESKTOP,WA_ERROR);
                   WinMessageBox(HWND_DESKTOP, hwnd,
                                 "Cannot create a NULL EA name.",
                                 NULL, 0, MB_OK);
                   return 0L;
                }

                GetMem(szScratch,MAXEAVALUE+1);  /* Allocate buffer for value
                WinQueryDlgItemText(hwnd, IDD_EAVALUE, MAXEAVALUE+1, szScratch

                if(!strlen(szScratch))    /* They nulled out the value */
                {
                   FreeMem(szScratch,MAXEAVALUE+1); /* Free the value buffer *
                   WinAlarm(HWND_DESKTOP,WA_ERROR);
                   WinMessageBox(HWND_DESKTOP, hwnd,
                                 "An EA can't have a NULL value.",
                                 NULL, 0, MB_OK);
                   return 0L;
                }

                if(stricmp(szEAName,pPDat->Point)) /* changed the EA name */
                {
                   if(EAExists(szEAName))  /* Can't have duplicate EA names */
                   {
                      WinAlarm(HWND_DESKTOP,WA_ERROR);
                      WinMessageBox(HWND_DESKTOP, hwnd,
                             "EA name already exists.  Can't create that name.
                             NULL, 0, MB_OK);

                      FreeMem(szScratch,MAXEAVALUE+1);  /* Free value buffer *
                      return 0L;
                   }
                }

                ResizeMem(szScratch,MAXEAVALUE+1,/* Resize buf to actual size
                          strlen(szScratch)+1);

                WinDismissDlg(hwnd, TRUE);
                return 0L;

             case DID_CANCEL:
                WinDismissDlg(hwnd, FALSE);
                return 0L;
          }
          break;
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: IconDlgProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              No user data is expected.
  *
  * Returns: Currently just calls WinDefDlgProc.
  *
  * Purpose: This proc is unimplemented, but is intedned to handle displaying
  *          and editing icons/bitmaps.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 MRESULT EXPENTRY IconDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    switch(msg)
    {
       case WM_INITDLG:
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:
                WinDismissDlg(hwnd, TRUE);
                return 0L;
          }
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: MultiTypeProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              Expects a user pointer to a PassData structure with Point
  *              member pointing to the current pFEA structure.  The Multi
  *              member fields of the PassData struct should also be
  *              filled in to indicate the location and size of the current
  *              m-m field.
  *
  * Returns: Always returns TRUE thru WinDismissDlg when DONE is clicked.
  *
  * Purpose: This proc handles the multi-type EA stuff.  Allows the fields
  *          in a given m-m to be edited.
  *
  * Usage/Warnings:  NOTE that this procedure is fully reentrant/recursive
  *                  in that it calls EditEAValue() which can call
  *                  MultiTypeProc.  Since PassData information is placed
  *                  in static variables, the ReEnter structure is used to
  *                  hold a dynamic linked list of values passed into the
  *                  proc and thus keeps the static data set to the proper
  *                  values.
  *
  * Calls: MultiTypeIndex(), EAValueString(), EditEAValue(), MultiAdd()
  */

 MRESULT EXPENTRY MultiTypeProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    static HOLDFEA *pFEA;        /* Points to the current EA         */
    static PASSDATA FAR *pPDat;  /* Points to the passed Data struct */
    static REENTER *CurEntry;    /* Points to most recent data frame */
    REENTER *ReEntHold;          /* Temp used to setup the frame     */
    PASSDATA PDat;               /* Used for call to EditEAValue     */
    USHORT *pusPtr,cnt;          /* Utility vars                     */
    SHORT  sOffset;
    CHAR   *pInsert,*pValue,*pDelete;
    CHAR   szCodePage[LENUSHORTBUF];
    BYTE   fNeed;                /* Holds state of need checkbox     */

    switch(msg)
    {
       case WM_INITDLG:
          pPDat = PVOIDFROMMP(mp2);             /* Set up static vars        *
          pFEA  = (HOLDFEA *) pPDat->Point;

          GetMem(ReEntHold,sizeof(REENTER));    /* Allows for recusive calls *
          ReEntHold->pPDat = pPDat;
          ReEntHold->pFEA  = pFEA;
          ReEntHold->next  = CurEntry;
          CurEntry = ReEntHold;

          WinSendDlgItemMsg(hwnd, IDD_NEEDBIT, BM_SETCHECK, /* Need bit setup
                            MPFROM2SHORT((pFEA->fEA&0x80)? TRUE : FALSE,0),
                            NULL);

          WinSetDlgItemText(hwnd,IDD_EATYPE,"");  /* Setup the codepage */
          WinSetDlgItemText(hwnd,IDD_EANAME,pFEA->szName);
          WinSendDlgItemMsg(hwnd, IDD_CODEPAGE,EM_SETTEXTLIMIT,
                                  MPFROM2SHORT(LENUSHORTBUF-1,0),NULL);

          pusPtr= (USHORT *) ((CHAR *) pFEA->aValue+pPDat->usMultiOffset);
          pusPtr++;       /* Skip the EA_Type field and point to codepage */
          sprintf(szCodePage,"%u",*pusPtr);
          WinSetDlgItemText(hwnd, IDD_CODEPAGE,szCodePage);

          pusPtr++;      /* Skip codepage and point to the field count */

          for(cnt=0;cnt< *pusPtr;cnt++) /* Add each field to the L-Box */
          {
             pInsert = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset,cnt);
             pValue = EAValueString(hwnd,pInsert); /* Ptr to asciiz string */

             WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_INSERTITEM,
                               MPFROM2SHORT(LIT_END,0),
                               MPFROMP(pValue));
             FreeMem(pValue,strlen(pValue)+1);  /* Free asciiz string */
          }
          return 0L;

       case WM_CONTROL:
          switch(SHORT1FROMMP(mp1))
          {
             case IDD_LBOX:
                switch(SHORT2FROMMP(mp1))
                {
                   case LN_SELECT:   /* Display proper EA type for selection *
                      sOffset = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_LBOX
                                                        LM_QUERYSELECTION,0,0)
                      if(sOffset<0)
                         break;

                      pValue = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffse
                                              sOffset);
                      pusPtr = (USHORT *) pValue;

                      WinSetDlgItemText(hwnd,IDD_EATYPE,
                                 ConvTable[LookupEAType(*pusPtr)].szFormat);
                      break;

                   case LN_ENTER:  /* Setup and edit a m-m field */
                      sOffset = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_LBOX
                                                        LM_QUERYSELECTION,0,0)
                      if(sOffset<0)
                         return 0L;

                      PDat.Point         = (CHAR *) pFEA; /* Pass curr data */
                      PDat.cbMulti       = pPDat->cbMulti;
                      PDat.usMultiOffset = pPDat->usMultiOffset;
                      PDat.usIndex       = (USHORT) sOffset;

                      if(EditEAValue(hwnd,&PDat)) /* They didn't cancel */
                      {
                         pInsert=MultiTypeIndex(pFEA->aValue
                                                 + pPDat->usMultiOffset,
                                                sOffset);
                         pValue =EAValueString(hwnd,pInsert);

                         WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_SETITEMTEXT,
                                           MPFROMSHORT(sOffset),
                                           MPFROMP(pValue));
                         FreeMem(pValue,strlen(pValue)+1); /* Free asciiz str
                      }
                      return 0L;
                }
          }

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case IDD_ADD:                  /* Add an item for a m-m */
                MultiAdd(hwnd, pFEA,pPDat);
                return 0L;

             case IDD_EDIT:  /* Setup and edit a m-m field */
                sOffset = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_LBOX,
                                                    LM_QUERYSELECTION,0,0));
                if(sOffset<0)
                   return 0L;

                PDat.Point         = (CHAR *) pFEA; /* Pass curr data */
                PDat.cbMulti       = pPDat->cbMulti;
                PDat.usMultiOffset = pPDat->usMultiOffset;
                PDat.usIndex       = (USHORT) sOffset;

                if(EditEAValue(hwnd,&PDat)) /* They didn't cancel */
                {
                   pInsert=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                          sOffset);
                   pValue =EAValueString(hwnd,pInsert);

                   WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_SETITEMTEXT,
                                     MPFROMSHORT(sOffset),
                                     MPFROMP(pValue));
                   FreeMem(pValue,strlen(pValue)+1); /* Free asciiz str */
                }
                return 0L;

             case IDD_DELETE:               /* Delete hi-lited item  */
                sOffset = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, IDD_LBOX,
                                                     LM_QUERYSELECTION,0,0));
                if(sOffset<0)   /* No item is currently selected */
                   return 0L;

                WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_DELETEITEM,
                                  MPFROMSHORT(sOffset),0L);

                /* Get pointers to start of selected field and the following
                   field, then move the rest of the EA back, resize the
                   buffer, and change the EA Value counter                  */

                pDelete = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset,
                                         sOffset);
                pValue  = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset,
                                         sOffset+1);
                memmove(pDelete,pValue,pFEA->cbValue-(pValue-pFEA->aValue));
                ResizeMem(pFEA->aValue,pFEA->cbValue,
                          pFEA->cbValue-(pValue-pDelete));
                pFEA->cbValue -= pValue-pDelete;

                /* Decrement the field count in the m-m */
                pusPtr  = (USHORT *) ((CHAR *) pFEA->aValue +
                                               pPDat->usMultiOffset);
                pusPtr+=2;         /* Point to the field count */
                *pusPtr -= 1;      /* Decrement the field cnt  */

                FILE_CHANGED = TRUE;
                return 0L;

             case DID_DONE:
                /* Handle the possible change of the need checkbox */
                fNeed = (BYTE) SHORT1FROMMR(WinSendDlgItemMsg(hwnd, IDD_NEEDBI
                                                 BM_QUERYCHECK,
                                                 0L, 0L));
                if(fNeed)
                   fNeed = 0x80;
                if(fNeed != (pFEA->fEA & (BYTE) 0x80)) /* Need changed */
                {
                   pFEA->fEA = (pFEA->fEA & (BYTE) 0x7F) | fNeed;
                   FILE_CHANGED = TRUE;
                }

                /* Handle the possible change of the codepage */
                WinQueryDlgItemText(hwnd, IDD_CODEPAGE,
                                    LENUSHORTBUF, szCodePage);
                sscanf(szCodePage,"%u",&cnt);
                pusPtr= (USHORT *) ((CHAR *) pFEA->aValue+pPDat->usMultiOffset
                pusPtr++;    /* Skip the EA_Type field and point to codepage *
                if(*pusPtr != cnt) /* They changed the codepage value */
                {
                   *pusPtr = cnt;
                   FILE_CHANGED = TRUE;
                }

                ReEntHold = CurEntry->next;       /* Finish recursion safety *
                FreeMem(CurEntry,sizeof(REENTER));
                CurEntry = ReEntHold;
                if(CurEntry)
                {
                   pPDat = CurEntry->pPDat;
                   pFEA  = CurEntry->pFEA;
                }
                WinDismissDlg(hwnd, TRUE);
                return 0L;
          }
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: ShowEAType()
  *
  * Parameters:  hwnd which is the current window handle.
  *
  * Returns: VOID
  *
  * Purpose: This routine handles the display of the current EA type by
  *          setting the text in the appropriate dialog field.
  *
  * Usage/Warnings:  Routine should be bullet proof as it does its own
  *                  error checking.  It assumes that hwnd points to the
  *                  correct window with the name listbox in it.
  *
  * Calls: GetCurFEA(), LookupEAType()
  */

 VOID ShowEAType(HWND hwnd)
 {
    USHORT usEAType;         /* Holds the offset of EAType into ConvTable */
    HOLDFEA *pFEA;

    pFEA = GetCurFEA(hwnd,pHoldFEA); /* Points to the selected EA */
    if(!pFEA)                        /* Kill line if nothing is selected */
    {
       WinSetDlgItemText(hwnd,IDD_EATYPE,"");
       return;
    }
    usEAType = LookupEAType((USHORT) *pFEA->aValue);

    WinSetDlgItemText(hwnd,IDD_EATYPE,ConvTable[usEAType].szFormat);
 }



 /*
  * Function name: EditEA()
  *
  * Parameters:  hwnd which is the current window handle.
  *
  * Returns: TRUE iff the edit was successful.
  *
  * Purpose: This routine handles the editing of an EA when it is selected
  *          from the listbox.  It get the item selected, sets up the PassData
  *          structure then calls EditEAValue to do the actual edit.
  *
  * Usage/Warnings:  Routine should be bullet proof as it does its own
  *                  error checking.  It assumes that hwnd points to the
  *                  correct window with the name listbox in it.
  *
  * Calls: EditEAValue()
  */

 BOOL EditEA(HWND hwnd)
 {
    HOLDFEA *pFEA=pHoldFEA;  /* Pointer for selected EA */
    LONG offset,lCnt;        /* Both used to reference offset of selected EA *
    PASSDATA PData;

    if(!FILE_ISOPEN)
       return(FALSE);

    offset = lCnt = (LONG) WinSendDlgItemMsg(hwnd, IDD_LBOX,
                                             LM_QUERYSELECTION,0,0);

    if(offset<0)        /* Nothing was selected */
       return(FALSE);

    while(lCnt--)         /* Get to the right EA */
       pFEA = pFEA->next;

    PData.Point         = (CHAR *) pFEA;        /* Set the pass data struct */
    PData.usMultiOffset = 0;
    PData.cbMulti       = 0;

    if(EditEAValue(hwnd,&PData))  /* It worked */
    {
       WinSendDlgItemMsg(hwnd, IDD_LBOX,LM_SETITEMTEXT,
                         MPFROMSHORT((SHORT) offset),MPFROMP(pFEA->szName));
       return(TRUE);
    }
    return(FALSE);
 }


 /* This routine returns a pointer to the EA currently highlighted in the
    list box. */

 /*
  * Function name: GetCurFEA()
  *
  * Parameters:  hwnd which is the current window handle.
  *              pFEA which points to the base FEA in the linked list.
  *
  * Returns: The current EA as determined by querying the l-box selector,
  *          returns NULL if there is an error or nothing is selected.
  *
  * Purpose: This routine returns a pointer to the EA currently highlighted
  *          in the list box.
  *
  * Usage/Warnings:  Routine should be bullet proof as it does its own
  *                  error checking.  It assumes that hwnd points to the
  *                  correct window with the name listbox in it.
  *
  * Calls: EditEAValue()
  */

 HOLDFEA *GetCurFEA(HWND hwnd, HOLDFEA *pFEA)
 {
    LONG lOffset;

    lOffset = (LONG) WinSendDlgItemMsg(hwnd, IDD_LBOX,
                                       LM_QUERYSELECTION,0,0);

    if(lOffset<0)      /* Nothing is highlighted */
       return(NULL);

    while(lOffset--)
    {
       pFEA = pFEA->next;
    }

    return(pFEA);
 }




 NEADEA.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\NEAD\NEADEA.C

 /*************************************************************

  This module contains subroutines for nead.c that specifically
  deal with the manipulation of EAs.

  Procedures in this file:
    AddEA()             Handles the Add button press
    QueryEAs()          Reads in all the EAs associated with a file
    CheckEAIntegrity()  Checks an EA buffer to see if it is valid
    Free_FEAList()      Deallocates memory associated with EA list
    LookupEAType()      Gets an offset into table for an EA type
    DeleteCurEA()       Deletes the highlighted EA
    CurEAType()         Returns EA Type given a HoldFEA ptr
    GetUSHORT()         Returns nth USHORT in ->aValue
    WriteEAs()          Updates EAs state on the disk
    EditEAValues()      Handles editing a given EA (also m-m EAs)
    EAExists()          Determines if the given EA Name exists
    ChangeName()        Handles the change of an EA's name
    MultiTypeIndex()    Gets a specific field in a m-m structure
    EAValueString()     Returns a representational string for an EA
    MultiAdd()          Handles addition of a field for m-m

 **************************************************************/

 #include "nead.h"

 /************ External GLOBALS *******************************/

 extern CHAR szFileName[CCHMAXPATH];
 extern CHAR szEAName[MAXEANAME+1];
 extern USHORT usRetEAType;
 extern BOOL FILE_ISOPEN;
 extern BOOL FILE_CHANGED;
 extern BOOL COMMAND_LINE_FILE;
 extern HHEAP hhp;
 extern CHAR *pAlloc,*szEditBuf,*szAscii,*szScratch;
 extern HOLDFEA *pHoldFEA;
 extern DELETELIST *pDelList;
 extern EADATA ConvTable[EATABLESIZE];

 /*************************************************************/


 /*
  * Function name: AddEA()
  *
  * Parameters:  hwnd which is the current window handle.
  *
  * Returns: TRUE iff the EA is successfully added.
  *
  * Purpose: This routine handles the addition of a new EA to the linked list.
  *
  * Usage/Warnings:  Routine does NOT do full memory error trapping and the
  *                  insert message to the l-box is not error checked.
  *
  * Calls: EditEAValue()
  */

 BOOL AddEA(HWND hwnd)
 {
    HOLDFEA  *pFEA=pHoldFEA;  /* Points to the beginning of the EA list */
    HOLDFEA  *pNewFEA;        /* Used to temporarily hold the new EA    */
    PASSDATA PData;

    if(!FILE_ISOPEN)
       return(FALSE);

    if(!WinDlgBox(HWND_DESKTOP,         /* get new EA name and type */
                  hwnd,
                  AddEAProc,
                  (HMODULE)NULL,
                  IDD_ADDEA,
                  NULL))
       return(FALSE);                   /* they said cancel */

    GetMem(pNewFEA, sizeof(HOLDFEA));   /* Allocate space for new EA struct */

    pNewFEA->cbName = (CHAR) strlen(szEAName);  /* Fill in new structure */
    pNewFEA->cbValue= 0;
    pNewFEA->fEA    = 0; /* Need bit NOT set */

    GetMem(pNewFEA->szName,pNewFEA->cbName+1);  /* Name returned in szEAName *
    strcpy(pNewFEA->szName,strupr(szEAName));
    pNewFEA->aValue = NULL;
    pNewFEA->next   = NULL;

    if(pHoldFEA == NULL)           /* It's the first EA for the file */
    {
       pHoldFEA = pNewFEA;
    }
    else                           /* Add EA to the end of the linked list */
    {
       while(pFEA->next)
          pFEA = pFEA->next;
       pFEA->next = pNewFEA;
    }
    PData.Point         = (CHAR *) pNewFEA;  /* Setup user data for call */
    PData.cbMulti       = 0;                 /* to edit of the name and  */
    PData.usMultiOffset = 0;                 /* EA Value                 */

    if(!EditEAValue(hwnd,&PData)) /* They canceled the edit */
    {
       if(pFEA)               /* It's not the only EA          */
          pFEA->next = NULL;  /* Disconnect the partial new EA */
       else
          pHoldFEA = NULL;    /* The new EA was the first one  */

       FreeMem(pNewFEA->szName,pNewFEA->cbName+1);
       FreeMem(pNewFEA,sizeof(HOLDFEA));

       return(FALSE);
    }

    WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_INSERTITEM, /* Insert name in L-Box *
                      MPFROM2SHORT(LIT_END,0),
                      MPFROMP(pNewFEA->szName));

    return(TRUE);
 }


 /*
  * Function name: QueryEAs()
  *
  * Parameters:  hwnd which is the current window handle.
  *              pszPath points to the path of the file to grab EAs from.
  *
  * Returns: TRUE iff it successfully reads in the EAs.  Upon exit, global
  *          pHoldFEA points to a linked list of the EAs for the current file.
  *
  * Purpose: Call DOS to Query a file's EA names and values.
  *
  * Usage/Warnings:  Routine does NOT do full memory error trapping.
  *                  NOTE:  This routine does NOT prevent other processes
  *                  from accessing the file's EAs while it is reading them
  *                  in, or while the program is editing them.
  *
  * Calls: Free_FEAList(), CheckEAIntegrity()
  */

 BOOL QueryEAs(HWND hwnd,CHAR *pszPath)
 {
    CHAR *pAlloc;       /* Holds the FEA struct returned by DosEnumAttribute *
                        /* also used to create the GEALIST for DosQPathInfo  *
    CHAR *pBigAlloc;    /* Temp buffer to hold each EA as it is read in      *
    USHORT cbBigAlloc;  /* Size of buffer                                    *

    ULONG ulEntryNum = 1; /* count of current EA to read (1-relative)        *
    ULONG ulEnumCnt;      /* Number of EAs for Enum to return, always 1      *

    HOLDFEA *pLastIn;     /* Points to last EA added, so new EA can link     *
    HOLDFEA *pNewFEA;     /* Struct to build the new EA in                   *

    FEA *pFEA;            /* Used to read from Enum's return buffer          *
    GEALIST *pGEAList;    /* Ptr used to set up buffer for DosQPathInfo call *
    EAOP eaopGet;         /* Used to call DosQPathInfo                       *

    GetMem((HHEAP) pAlloc,(USHORT) MAX_GEA);     /* Allocate enough room for a
    pFEA = (FEA *) pAlloc;      /* pFEA always uses pAlloc buffer            *

    pHoldFEA = NULL;            /* Reset the pointer for the EA linked list  *

    while(TRUE) /* Loop continues until there are no more EAs */
    {
       ulEnumCnt = 1;                 /* Only want to get one EA at a time */
       if(DosEnumAttribute(Ref_ASCIIZ,          /* Read into pAlloc Buffer */
                           pszPath,             /* Note that this does not */
                           ulEntryNum,          /* get the aValue field,   */
                           pAlloc,              /* so DosQPathInfo must be */
                           MAX_GEA,             /* called to get it.       */
                           &ulEnumCnt,
                           (LONG) GetInfoLevel1,
                           0L))
         break;                         /* There was some sort of error    */

       if(ulEnumCnt != 1)               /* All the EAs have been read      */
          break;

       ulEntryNum++;

       GetMem(pNewFEA,sizeof(HOLDFEA));

       if (pNewFEA == NULL)             /* Out of memory */
       {
          FreeMem(pAlloc,MAX_GEA);
          Free_FEAList(pHoldFEA,pDelList);
          return (FALSE);
       }

       pNewFEA->cbName = pFEA->cbName;  /* Fill in the HoldFEA structure   */
       pNewFEA->cbValue= pFEA->cbValue;
       pNewFEA->fEA    = pFEA->fEA;
       pNewFEA->next = NULL;

       GetMem(pNewFEA->szName,pFEA->cbName +1); /* Allocate for 2 arrays   */
       GetMem(pNewFEA->aValue,pFEA->cbValue);

       if (!pNewFEA->szName || !pNewFEA->aValue) /* Out of memory */
       {
          if(pNewFEA->szName)
             FreeMem(pNewFEA->szName,pFEA->cbName+1);
          if(pNewFEA->aValue)
             FreeMem(pNewFEA->aValue,pFEA->cbValue);

          FreeMem(pAlloc, MAX_GEA);
          FreeMem(pNewFEA,sizeof(HOLDFEA));

          Free_FEAList(pHoldFEA,pDelList);
          return (FALSE);
       }
       strcpy(pNewFEA->szName,pAlloc+sizeof(FEA));      /* Copy in EA Name */

       cbBigAlloc = sizeof(FEALIST) + pNewFEA->cbName+1 + pNewFEA->cbValue;
       GetMem(pBigAlloc,cbBigAlloc);
       if (pBigAlloc == NULL)
       {
          FreeMem(pNewFEA->szName,pFEA->cbName+1);
          FreeMem(pNewFEA->aValue,pFEA->cbValue);
          FreeMem(pAlloc, MAX_GEA);
          FreeMem(pNewFEA,sizeof(HOLDFEA));
          Free_FEAList(pHoldFEA,pDelList);
          return (FALSE);
       }

       pGEAList = (GEALIST *) pAlloc;   /* Set up GEAList structure */

       pGEAList->cbList = sizeof(GEALIST) + pNewFEA->cbName; /* +1 for NULL */
       pGEAList->list[0].cbName = pNewFEA->cbName;
       strcpy(pGEAList->list[0].szName,pNewFEA->szName);

       eaopGet.fpGEAList = (GEALIST far *) pAlloc;
       eaopGet.fpFEAList = (FEALIST far *) pBigAlloc;

       eaopGet.fpFEAList->cbList = cbBigAlloc;

       DosQPathInfo(pszPath,            /* Get the complete EA info        */
                    GetInfoLevel3,
                    (PVOID) &eaopGet,
                    sizeof(EAOP),
                    0L);
       memcpy(pNewFEA->aValue,          /* Copy the value to HoldFEA       */
              pBigAlloc+sizeof(FEALIST)+pNewFEA->cbName+1,
              pNewFEA->cbValue);

       FreeMem(pBigAlloc,cbBigAlloc);   /* Release the temp Enum buffer    */

       if(!CheckEAIntegrity(pNewFEA->aValue,pNewFEA->cbValue)) /* Bad EA   */
       {
          FreeMem(pNewFEA->szName,pFEA->cbName+1);
          FreeMem(pNewFEA->aValue,pFEA->cbValue);
          FreeMem(pNewFEA,sizeof(HOLDFEA));
          continue;                    /* Don't add this EA to linked list */
       }

       if(pHoldFEA == NULL)             /* If first EA, set pHoldFEA       */
          pHoldFEA = pNewFEA;
       else                             /* Otherwise, add to end of list   */
          pLastIn->next = pNewFEA;

       pLastIn = pNewFEA;               /* Update the end of the list      */
    }
    FreeMem(pAlloc, MAX_GEA);  /* Free up the GEA buf for DosEnum */
    return (TRUE);
 }


 /*
  * Function name: CheckEAIntegrity()
  *
  * Parameters:  aBuf points to the buffer to check for a valid EA.
  *              cbBuf is the allocated length of aBuf.
  *
  * Returns: TRUE iff the buffer is a valid EA structure.
  *
  * Purpose: This routine checks the integrity of the passed EA buffer by
  *          seeing if there are any non-standard EA types, or bad data that
  *          isn't sized correctly.
  *
  * Usage/Warnings:  Routine uses MultiTypeIndex() to check m-m type EAs
  *                  since they are potentially recursive.  However, this
  *                  may not be a good idea if the m-m EA is severly
  *                  corrupted because it may cause MTI() to attempt a read
  *                  from protected memory.  Routine does NOT modify the
  *                  buffer under any circumstance.
  *
  * Calls: MultiTypeIndex()
  */

 BOOL CheckEAIntegrity(CHAR *aBuf,USHORT cbBuf)
 {
    USHORT *pusPtr = (USHORT *) aBuf;
    USHORT usOffset;
    CHAR   *aEndPtr;

    usOffset = LookupEAType(*pusPtr);  /* Get the EA type */

    switch(ConvTable[usOffset].usFldType)
    {
       case IDD_LPDATA:
          pusPtr++;
          if(*pusPtr + 2*sizeof(USHORT) == cbBuf)
             return TRUE;
          else
             return FALSE;

       case IDD_MULTILIST:
          if(*pusPtr == EA_MVMT)
          {
             pusPtr += 2;

             /* This checks where the end of the m-m list ends to determine
                the size of the EA.  This is probably not good if the EA is
                badly corrupted and it points to protected memory */

             aEndPtr = MultiTypeIndex(aBuf,*pusPtr);
             if(aEndPtr - aBuf == (SHORT) cbBuf)
                return TRUE;
             else
                return FALSE;
          }
          else /* Single type, multi-value is not yet implemented */
          {
             return TRUE;
          }
       default:
          return FALSE;
    }
    return TRUE;
 }


 /*
  * Function name: Free_FEAList()
  *
  * Parameters:  pFEA points to the beginning of the linked list to be freed.
  *              pDList points to the beginning of the deleted linked list.
  *
  * Returns: VOID.  The two linked lists passed in are cleaned out though.
  *
  * Purpose: This routine frees up the current list of EAs by deallocating
  *          the space used by the szName and aValue fields, then deallocating
  *          the HoldFEA struct.  Next, it deletes the EAName space, then the
  *          DeleteList structure.
  *
  * Usage/Warnings:  Note that NEAD always passes in pHoldFEA and pDelList
  *                  which is unnecessary since they are global pointers;
  *                  however, this is done to make the routine more flexible
  *                  by allowing multiple linked lists to exist.
  *
  * Calls:
  */

 VOID Free_FEAList(HOLDFEA *pFEA,DELETELIST *pDList)
 {
    HOLDFEA *next;  /* Holds the next field since we free the structure */
                    /* before reading the current next field            */
    DELETELIST *Dnext; /* Same purpose as *next */

    while(pFEA)
    {
       next = pFEA->next;
       if(pFEA->szName)                         /* Free if non-NULL name  */
          FreeMem(pFEA->szName,pFEA->cbName+1);
       if(pFEA->aValue)                         /* Free if non-NULL value */
          FreeMem(pFEA->aValue,pFEA->cbValue);

       FreeMem(pFEA,sizeof(HOLDFEA));           /* Free HoldFEA struct    */
       pFEA = next;
    }

    while(pDList)
    {
       Dnext = pDList->next;
       if(pDList->EAName)
          FreeMem(pDList->EAName,strlen(pDList->EAName)+1);
       FreeMem(pDList,sizeof(DELETELIST));
       pDList = Dnext;
    }
 }


 /*
  * Function name: LookupEAType()
  *
  * Parameters:  usType is tye EA type to be looked up.
  *
  * Returns: An offset into the ConvTable to the appropriate entry.  If no
  *          match is found, the return value points to the last entry,
  *          non-conventional format.
  *
  * Purpose: This routine takes EA type and returns an offset into ConvTable
  *          which points to an entry that describes the type passed in.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 USHORT LookupEAType(USHORT usType)
 {
    USHORT cnt;

    for(cnt=0;cnt<EATABLESIZE-1;cnt++)
       if(ConvTable[cnt].usPrefix == usType)
          return(cnt);
    return(cnt);
 }


 /*
  * Function name: DeleteCurEA()
  *
  * Parameters:  hwnd is the current window handle.
  *
  * Returns: VOID.  Removes one item from global pHoldFEA list and adds one
  *          to global pDelList.
  *
  * Purpose: This routine removes in memory the currently highlighted EA from
  *          the EA list.  It places the deleted EA in the global pDelList
  *          linked list.
  *
  * Usage/Warnings:  The memory allocation routines are NOT fully error trappe
  *
  * Calls: GetCurFEA()
  */

 VOID DeleteCurEA(HWND hwnd)
 {
    HOLDFEA *pFEA,*pFEAPrev;
    DELETELIST *pDL,*pDLcnt;  /* Utility ptrs for manipulating the Del list */
    LONG lOffset;

    pFEA = GetCurFEA(hwnd,pHoldFEA);    /* Gets a pointer to item to delete */
    if (pFEA == NULL)
       return;

    /* These two allocations should be checked for out of memory */
    GetMem(pDL,sizeof(DELETELIST));     /* Add Name to Delete List */
    GetMem(pDL->EAName,pFEA->cbName+1);
    strcpy(pDL->EAName,pFEA->szName);
    pDL->next = NULL;

    if(pDelList == NULL)        /* The del list was previously empty  */
       pDelList = pDL;
    else                        /* tack name onto the end of the list */
    {
       pDLcnt = pDelList;
       while(pDLcnt->next)
          pDLcnt = pDLcnt->next;
       pDLcnt->next = pDL;
    }

    lOffset = (LONG) WinSendDlgItemMsg(hwnd, IDD_LBOX,
                                      LM_QUERYSELECTION,0,0);
    WinSendDlgItemMsg(hwnd, IDD_LBOX,
                      LM_DELETEITEM,MPFROMSHORT((SHORT) lOffset),0L);

    if(lOffset<1)               /* Remove pFEA from the linked list */
    {
       pHoldFEA = pFEA->next;
    }
    else
    {
       pFEAPrev = pHoldFEA;

       while(--lOffset)              /* Find previous EA */
          pFEAPrev = pFEAPrev->next;

       pFEAPrev->next = pFEA->next;
    }

    FreeMem(pFEA->szName,pFEA->cbName+1);  /* Release the memory */
    FreeMem(pFEA->aValue,pFEA->cbValue);
    FreeMem(pFEA,sizeof(HOLDFEA));

    FILE_CHANGED = TRUE;
 }


 /*
  * Function name: CurEAType()
  *
  * Parameters:  pFEA points to the FEA struct to use.
  *
  * Returns: The EA type.
  *
  * Purpose: Given an EA structure, this routine returns the Type of the EA
  *          which resides in the first USHORT of the aValue member.
  *          This function is the same as GetUSHORT(pFEA,0)
  *
  * Usage/Warnings:  Assumes a valid HoldFEA struct.
  *
  * Calls:
  */

 USHORT CurEAType(HOLDFEA *pFEA)   /* Same as GetUSHORT(,0); */
 {
    USHORT *pusType;    /* EA Type is stored in first USHORT of aValue field *

    pusType = (USHORT *) pFEA->aValue;
    return(*pusType);
 }


 /*
  * Function name: GetUSHORT()
  *
  * Parameters:  pFEA points to the FEA struct to use.
  *              index is an offset of the USHORT to be returned
  *
  * Returns: The appropriate USHORT from the aValue field
  *
  * Purpose: This routine returns the nth USHORT value in the aValue member
  *          of pFEA using index as the offset.
  *
  * Usage/Warnings:  Assumes a valid HoldFEA struct and that index doesn't
  *                  point outside the aValue buffer.
  *
  * Calls:
  */

 USHORT GetUSHORT(HOLDFEA *pFEA,USHORT index)
 {
    USHORT *pusType;

    pusType = (USHORT *) pFEA->aValue;
    while(index-- > 0)
       pusType++;
    return(*pusType);
 }


 /*
  * Function name: WriteEAs()
  *
  * Parameters:  hwnd is the current window handle used only by memory
  *              allocation error handling.
  *
  * Returns: VOID.  But cleans out the pDelList linked list.
  *
  * Purpose: This routine updates the EAs on disk to reflect their current
  *          condition in memory.  First, all EAs in the delete list are
  *          removed from the disk, then all EAs in the pHoldFEA list are
  *          written out to disk.
  *
  * Usage/Warnings:  NOTE:  This routine is not bulletproof as it does not get
  *                  exclusive access to the file EAs, nor does it handle out
  *                  of disk space sort of errors. Also, memory fetches are
  *                  not fully error trapped.
  *
  * Calls:
  */

 VOID WriteEAs(HWND hwnd)
 {
    DELETELIST *pDL = pDelList,*pDLnext;
    HOLDFEA    *pHFEA= pHoldFEA;
    EAOP       eaopWrite;
    CHAR       aBuf[MAX_GEA],*aPtr;
    FEA        *pFEA = (FEA *) &aBuf[sizeof(ULONG)];
    USHORT     usRet,usMemNeeded;
    ULONG      *pulPtr=(ULONG *) aBuf; /* Initally points to top of FEALIST */

    if(!FILE_ISOPEN || !FILE_CHANGED) /* Don't write unless it's necessary */
       return;

    eaopWrite.fpFEAList = (FEALIST far *) aBuf; /* Setup fields that won't */
    pFEA->fEA     = 0;                          /* change for the delete   */
    pFEA->cbValue = 0;                          /* calls to DosSetPathInfo */

    while(pDL)                       /* Clean out all the deleted EA names */
    {
       pFEA->cbName = (UCHAR) strlen(pDL->EAName);
       *pulPtr      = sizeof(FEALIST) + pFEA->cbName+1; /* +1 for NULL */
       strcpy(aBuf+sizeof(FEALIST),pDL->EAName);

       usRet=DosSetPathInfo(szFileName,    /* Delete EA's by saying cbValue=0
                            SetInfoLevel2,
                            (PVOID) &eaopWrite,
                            (USHORT) sizeof(EAOP),
                            DSPI_WRTTHRU,
                            0L);

       pDLnext = pDL->next;                   /* Temp hold next pDL         */
       FreeMem(pDL->EAName, pFEA->cbName+1);  /* Free up current Del struct */
       FreeMem(pDL, sizeof(DELETELIST));
       pDL = pDLnext;                         /* Set pDL to saved value     */
    }
    pDelList = NULL;                          /* DelList is now empty       */

    while(pHFEA)   /* Go through each HoldFEA */
    {
       usMemNeeded = sizeof(FEALIST) + pHFEA->cbName+1 + pHFEA->cbValue;
       GetMem(aPtr,usMemNeeded);

       eaopWrite.fpFEAList = (FEALIST far *) aPtr; /* Fill in eaop struct */
       eaopWrite.fpFEAList->cbList = usMemNeeded;

       eaopWrite.fpFEAList->list[0].fEA     = pHFEA->fEA;
       eaopWrite.fpFEAList->list[0].cbName  = pHFEA->cbName;
       eaopWrite.fpFEAList->list[0].cbValue = pHFEA->cbValue;

       strcpy(aPtr + sizeof(FEALIST),  pHFEA->szName);
       memcpy(aPtr + sizeof(FEALIST) + pHFEA->cbName+1,
              pHFEA->aValue, pHFEA->cbValue);

       usRet=DosSetPathInfo(szFileName,             /* Write out the EA */
                            SetInfoLevel2,
                            (PVOID) &eaopWrite,
                            (USHORT) sizeof(EAOP),
                            DSPI_WRTTHRU,0L);

       FreeMem(aPtr,usMemNeeded);       /* Free up the FEALIST struct */

       pHFEA = pHFEA->next;
    }

    FILE_CHANGED = FALSE;
 }


 /*
  * Function name: EditEAValue()
  *
  * Parameters:  hwnd is the current window handle.
  *              pPDat is a pointer to PassData which contains Edit EA info.
  *
  * Returns: TRUE iff the edit was successful.
  *
  * Purpose: This routine allows the entry/edit of an EA value.
  *          condition in memory.  First, all EAs in the delete list are
  *          removed from the disk, then all EAs in the pHoldFEA list are
  *          written out to disk.
  *
  * Usage/Warnings:  Expects the PassData structure to tell it the HoldFEA
  *                  to edit, and if it is a subfield of a multi-multi EA,
  *                  the rest of the PassData structure will be filled in
  *                  to indicated which m-m is being edited.  Note that if
  *                  this is a new edit, usRetEAType is expected to be set
  *                  to the proper EA type upon entry.  NOTE:  memory sizing
  *                  requests are not fully error trapped.
  *
  * Calls: MultiTypeIndex(), ChangeName(), AsciiEditProc() (thru PM),
  *        MultiTypeProc() (thru PM).
  */

 BOOL EditEAValue(HWND hwnd, PASSDATA *pPDat)
 {
    USHORT usEAType; /* Holds the field type to be edited */
    USHORT *pusPtr;
    USHORT usSize;   /* Holds the delta difference of the old and new buffers
    CHAR   *szNew,*szTrash;     /* Temporary pointers */
    PASSDATA PDat;
    HOLDFEA *pFEA = (HOLDFEA *) pPDat->Point;   /* The EA to be edited */

    /* Determine the type of EA that will be edited */
    if(pPDat->cbMulti)                  /* It's a multi-type job  */
    {
       pusPtr = (USHORT *) MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset,
                                          pPDat->usIndex);
       usEAType = *pusPtr;
    }
    else if(pFEA->cbValue) /* It isn't a new EA name */
    {
       pusPtr   = (USHORT *) pFEA->aValue;
       usEAType = *pusPtr;
    }
    else    /* It's a new EA */
    {
       usEAType = ConvTable[usRetEAType].usPrefix;
    }

    PDat.Point   = pFEA->szName;        /* General setup for AsciiEditProc */
    PDat.usIndex = pPDat->cbMulti ? 1 : 0;   /* =1 if there is a multi  */
    PDat.fFlag   = (BYTE) ((pFEA->fEA & 0x80) ? TRUE : FALSE);

    switch(usEAType)
    {
       case EA_ASCIIZ:
       case EA_ASCIIZFN:
       case EA_ASCIIZEA:
       case EA_ASN1:
          if(pPDat->cbMulti)            /* It is a multi-type field */
             szAscii=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                    pPDat->usIndex)
                     +sizeof(USHORT);
          else if(pFEA->cbValue)        /* There is a current value */
             szAscii=pFEA->aValue+sizeof(USHORT);
          else                                     /* It's a new EA */
             szAscii=NULL;


          if(!WinDlgBox(HWND_DESKTOP,        /* Do an ascii text edit */
                        hwnd,
                        AsciiEditProc,
                        (HMODULE) NULL,
                        IDD_ASCIIEDIT,
                        &PDat))
             return(FALSE);                  /* They said cancel */

          if(PDat.fFlag)            /* Handle the need/nice bit */
             PDat.fFlag = 0x80;
          if(PDat.fFlag != (BYTE) (PDat.fFlag & 0x80))
             FILE_CHANGED = TRUE;
          pFEA->fEA = (BYTE) (pFEA->fEA & 0x7f) | PDat.fFlag;

          if(stricmp(strupr(szEAName),pFEA->szName)) /* The name changed */
             ChangeName(hwnd,pFEA,szEAName);

          if(pFEA->cbValue) /* There is a current value */
          {
             if(!strcmp(szAscii,szScratch))  /* It hasn't changed */
                return(TRUE);

             if(pPDat->cbMulti) /* Do the whole thing here if m-m */
             {
                usSize = strlen(szScratch)-strlen(szAscii); /* Change in size

                if(usSize > 0) /* The new string is longer */
                {
                   ResizeMem(pFEA->aValue,          /* Enlarge the EA size */
                             pFEA->cbValue,
                             pFEA->cbValue+usSize);
                   szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                          pPDat->usIndex+1);
                   memmove(szTrash+usSize,  /* Move end of EA to make room */
                           szTrash,
                           pFEA->cbValue-(szTrash-pFEA->aValue));
                }
                else /* The new string is shorter */
                {
                   szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                          pPDat->usIndex+1);
                   memmove(szTrash+usSize, /* Move back the end of the EA */
                           szTrash,
                           pFEA->cbValue-(szTrash-pFEA->aValue));
                   ResizeMem(pFEA->aValue,  /* Shrink the EA buffer */
                             pFEA->cbValue,
                             pFEA->cbValue+usSize);
                }
                szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                       pPDat->usIndex);
                strcpy(szTrash+sizeof(USHORT),szScratch); /* Copy in new val *
                pFEA->cbValue+=usSize;              /* Change buffer count   *

                return(FILE_CHANGED = TRUE);  /* Done with m-m edit */
             }
             else
             {
                FreeMem(pFEA->aValue,pFEA->cbValue); /* Release old Value mem
             }
          }
          GetMem(szNew,strlen(szScratch)+3);  /* +3 for Type & NULL */
          pusPtr = (USHORT *) szNew;
          *pusPtr= usEAType;                  /* Set type in new buffer
          strcpy(szNew+2,szScratch);          /* Copy in the new value
          pFEA->aValue = szNew;               /* Fix up the structure
          pFEA->cbValue= strlen(szScratch)+3;

          return(FILE_CHANGED = TRUE);

       case EA_LPBINARY:
       case EA_LPASCII:
       case EA_LPMETAFILE:
          if(pPDat->cbMulti)            /* It is a multi-type field */
          {  /* szTrash points to field to edit, pusPtr to the field length */
             szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                    pPDat->usIndex);
             pusPtr = (USHORT *) ((CHAR *) szTrash + sizeof(USHORT));
             usSize = *pusPtr;
             if(usSize)  /* It isn't a new EA */
             {
                GetMem(szAscii,usSize+1); /* Set up inital value for edit */
                memcpy(szAscii,szTrash+2*sizeof(USHORT),usSize);
                szAscii[usSize]=0;
             }
             else                       /* No inital value */
                szAscii = NULL;
          }
          else if(pFEA->cbValue)
          {
             usSize=GetUSHORT(pFEA,1);  /* Get size and set inital value */
             if(usSize)
             {
                GetMem(szTrash,usSize+1); /* +1 for null */
                memcpy(szTrash,pFEA->aValue+4,usSize);
                szTrash[usSize]=0;
                szAscii=szTrash;
             }
             else
                szAscii = NULL;
          }
          else
             szAscii = NULL;

          if(!WinDlgBox(HWND_DESKTOP,        /* Do an ascii text edit */
                        hwnd,
                        AsciiEditProc,
                        (HMODULE) NULL,
                        IDD_ASCIIEDIT,
                        &PDat))
          {  /* Cancel, but check if memory needs to be freed before exit */
             if(pPDat->cbMulti || pFEA->cbValue)
                if(szAscii) /* It's not NULL */
                   FreeMem(szAscii,strlen(szAscii)+1); /* +1 for NULL */

             return(FALSE);
          }

          if(PDat.fFlag)              /* Handle the need/nice bit */
             PDat.fFlag = 0x80;
          if(PDat.fFlag != (BYTE) (PDat.fFlag & 0x80))
             FILE_CHANGED = TRUE;
          pFEA->fEA = (BYTE) (pFEA->fEA & 0x7f) | PDat.fFlag;

          if(stricmp(strupr(szEAName),pFEA->szName)) /* The name changed */
             ChangeName(hwnd,pFEA,szEAName);

          if(pFEA->cbValue) /* There is a current value */
          {
             if(!strcmp(szAscii,szScratch))  /* It hasn't changed */
             {
                if(szAscii)
                   FreeMem(szAscii,usSize+1);
                return(TRUE);
             }
             if(szAscii)                  /* Free default value buffer */
                FreeMem(szAscii,usSize+1);

             if(pPDat->cbMulti)   /* Do the whole thing here is multi-type */
             {
                USHORT usDelta = strlen(szScratch) - usSize; /* Change in len
                if(usDelta > 0) /* The new string is longer, resize first */
                {
                   ResizeMem(pFEA->aValue,pFEA->cbValue,pFEA->cbValue+usDelta)
                   szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                          pPDat->usIndex+1);
                   memmove(szTrash+usDelta,szTrash,
                           pFEA->cbValue-(szTrash-pFEA->aValue));
                }
                else  /* move first, resize afterwards */
                {
                   szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                          pPDat->usIndex+1);
                   memmove(szTrash+usDelta,szTrash,
                           pFEA->cbValue-(szTrash-pFEA->aValue));
                   ResizeMem(pFEA->aValue,pFEA->cbValue,pFEA->cbValue+usDelta)
                }
                szTrash=MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                       pPDat->usIndex);
                memmove(szTrash+2*sizeof(USHORT),szScratch,strlen(szScratch));
                pusPtr = (USHORT *) ((CHAR *) szTrash + sizeof(USHORT));
                *pusPtr= strlen(szScratch);   /* Set the length field */


                pFEA->cbValue += usDelta;     /* Adjust struct len field */

               return(FILE_CHANGED = TRUE);
             }

             FreeMem(pFEA->aValue,pFEA->cbValue); /* Free up old value */
          }
          GetMem(szNew,strlen(szScratch)+4);    /* Get space for new value */
          pusPtr = (USHORT *) szNew;
          *pusPtr= usEAType;                    /* Set type field */
          pusPtr++;
          *pusPtr= strlen(szScratch);           /* Set length field */
          memcpy(szNew+4,szScratch,*pusPtr);    /* Copy in new value */
          pFEA->aValue = szNew;                 /* Adjust pointers */
          pFEA->cbValue= strlen(szScratch)+4;   /* +4 for type and LP cnt */

          return(FILE_CHANGED = TRUE);

       case EA_MVMT:                    /* It's multi-value multi-type */
          if(pFEA->cbValue == 0) /* It's a new EA */
          {
             GetMem(pFEA->aValue,3*sizeof(USHORT)); /* Allocate empty m-m EA *
             pFEA->cbValue = 3*sizeof(USHORT);
             pusPtr      = (USHORT *) pFEA->aValue;
             *pusPtr = 0xffdf;                 /* Multi-value, multi-type */
             pusPtr+=2;                        /* Skip type, codepage */
             *pusPtr = 0;                      /* No fields initially     */
             FILE_CHANGED = TRUE;
          }

          /* Set up passed in data */
          if(pPDat->cbMulti) /* It's a multi-type job  */
          {
             szNew   = MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                      pPDat->usIndex);
             szTrash = MultiTypeIndex(pFEA->aValue + pPDat->usMultiOffset,
                                      pPDat->usIndex+1);
             PDat.usMultiOffset = szNew - pFEA->aValue;
             PDat.cbMulti       = szTrash - szNew;
          }
          else
          {
             PDat.usMultiOffset = 0;
             PDat.cbMulti       = pFEA->cbValue;
          }
          PDat.Point         = (CHAR *) pFEA;

          WinDlgBox(HWND_DESKTOP,           /* Do the Multi-type edit */
                    hwnd,
                    MultiTypeProc,
                    (HMODULE) NULL,
                    IDD_MULTIBOX,
                    &PDat);
          return(TRUE);

    }
 }


 /*
  * Function name: EAExists()
  *
  * Parameters:  szEAName points to the EA Name to check for.
  *
  * Returns: TRUE iff an EA with a name matching szEAName exists.
  *
  * Purpose: This routine goes through the linked list pointed to by global
  *          pHoldFEA and determines whether or not an EA of the passed name
  *          already exists.
  *
  * Usage/Warnings:  The comparison is NOT case sensitive.
  *
  * Calls:
  *
  */

 BOOL EAExists(CHAR *szEAName)
 {
    HOLDFEA *phFEA=pHoldFEA;

    while(phFEA)
    {
       if(!stricmp(szEAName,phFEA->szName))
          return(TRUE);
       phFEA=phFEA->next;
    }
    return(FALSE);
 }


 /*
  * Function name: ChangeName()
  *
  * Parameters:  hwnd is the current window used for error messages.
  *              pFEA points to the current FEA.
  *              szName points to the new EA name.
  *
  * Returns: VOID.  Fixes up the pFEA structure and global pDelList.
  *
  * Purpose: This routine copies the current EA Name to the delete list, then
  *          allocates a new space, copies the new name into it, and sets the
  *          FEA pointer to it.
  *
  * Usage/Warnings:  NOTE:  Not all the memory allocations are fully error
  *                         trapped.
  *
  * Calls:
  *
  */

 VOID ChangeName(HWND hwnd,HOLDFEA *pFEA,CHAR *szName)
 {
    CHAR *szTemp;
    DELETELIST *pDL;

    GetMem(szTemp,strlen(szName+1));  /* Allocate space for new name */
    if(!szTemp)
       return;

    GetMem(pDL,(USHORT) sizeof(DELETELIST)); /* Allocate a new delete struct
    pDL->EAName = pFEA->szName;              /* Fill in DeleteList struct
    pDL->next   = pDelList;
    pDelList    = pDL;

    strcpy(szTemp,szName);                   /* Copy name to permanent buffer
    pFEA->szName = szTemp;                   /* Fix up struct
    pFEA->cbName = (CHAR) strlen(szName);

    FILE_CHANGED = TRUE;
 }


 /*
  * Function name: MultiTypeIndex()
  *
  * Parameters:  pMulti points to the current m-m field.
  *              usIndex is the field the caller is interested in.
  *
  * Returns: a pointer to the field specified by the usIndex param.
  *
  * Purpose: This routine takes a pointer to a Multi-Multi data field and
  *          returns a pointer to the nth data field in this buffer.
  *
  * Usage/Warnings:  NOTE:  Memory bounds are not checked and a corrupt
  *                         EA field could cause unspecified results.
  *                  Recursively calls itself to handle nesting. Does not
  *                  support multi-value single type fields.
  *
  * Calls: LookupEAType, MultiTypeIndex()
  *
  */

 CHAR *MultiTypeIndex(CHAR *pMulti, USHORT usIndex)
 {
    USHORT *pusPtr;
    USHORT usOffset;

    pMulti += 3*sizeof(USHORT);  /* skip over 0xffdf, codepage, and field cnt

    while(usIndex--)             /* loop to skip over correct # of flds  */
    {
       pusPtr   = (USHORT *) pMulti;
       usOffset = LookupEAType(*pusPtr);    /* Get offset of field type */

       pMulti += sizeof(USHORT);            /* Skip over the type field */

       switch(ConvTable[usOffset].usFldType)
       {
          case IDD_ASCIIZ:
             while(*pMulti++);              /* Increment to point after NULL *
             break;

          case IDD_LPDATA:
             pusPtr = (USHORT *) pMulti;                /* Get the length */
             pMulti += *pusPtr + sizeof(USHORT);        /* skip to end */
             break;

          case IDD_MULTILIST:
             if(*pusPtr == EA_MVMT) /* m-m, do a recursive call to skip fld */
             {
                pusPtr = (USHORT *) pMulti; /* points to field cnt */
                pMulti = MultiTypeIndex(pMulti-sizeof(USHORT),*pusPtr);
                break;
             }
             /* Not yet implemented for Multi-valued single-type stuff... */
             break;
       }
    }
    return(pMulti);
 }


 /*
  * Function name: EAValueString()
  *
  * Parameters:  hwnd is the current window handle.
  *              aEAVal is a pointer to an EA value field.
  *
  * Returns: a pointer to an ASCII description of the field value.
  *
  * Purpose: This routine takes a pointer to an EA Value (i.e. starting with
  *          with $ffxx) and returns a pointer to a string representing the
  *          value of the EA.  This string must be Freed by the user when
  *          finished with it.
  *
  * Usage/Warnings:  NOTE:  Not all GetMem's are totally error trapped.
  *                  The string returned is allocated in this procedure,
  *                  but it is the caller's responsibility to free the buffer.
  *
  * Calls:
  *
  */

 CHAR *EAValueString(HWND hwnd,CHAR *aEAVal)
 {
    USHORT *pusPtr= (USHORT *) aEAVal;  /* Points to EA Type     */
    CHAR *szRet,*szTemp;        /* szRet points to return string */

    switch(*pusPtr)
    {
       case EA_ASCIIZ:    /* For asciiz strings, return MAXSHOWSIZE-1 chars */
       case EA_ASCIIZFN:
       case EA_ASCIIZEA:
       case EA_ASN1:
          aEAVal += sizeof(USHORT);
          if(strlen(aEAVal)<MAXSHOWSIZE)
          {
             GetMem(szRet,strlen(aEAVal)+1);
             strcpy(szRet,aEAVal);
          }
          else
          {
             GetMem(szRet,MAXSHOWSIZE);
             strncpy(szRet,aEAVal,MAXSHOWSIZE-4);
             strcpy (szRet+MAXSHOWSIZE-4,"...");
             szRet[MAXSHOWSIZE-1]=0;
          }
          return(szRet);

       case EA_LPASCII:   /* Display up to first MAXSHOWSIZE-1 chars */
       case EA_LPMETAFILE:
          pusPtr++;
          aEAVal += 2*sizeof(USHORT);
          if(*pusPtr < MAXSHOWSIZE)
          {
             GetMem(szRet,*pusPtr +1);
             strncpy(szRet,aEAVal,*pusPtr);
             szRet[*pusPtr]=0;
          }
          else
          {
             GetMem(szRet,MAXSHOWSIZE);
             strncpy(szRet,aEAVal,MAXSHOWSIZE-4);
             strcpy (szRet+MAXSHOWSIZE-4,"...");
             szRet[MAXSHOWSIZE-1]=0;
          }
          return(szRet);

       /* For the rest of the types, just display the field type */

       case EA_LPBINARY:
          szTemp = "*** LP Binary ***";
          break;

       case EA_LPBITMAP:
          szTemp = "*** LP Bitmap ***";
          break;

       case EA_LPICON:
          szTemp = "*** LP Icon ***";
          break;

       case EA_MVMT:
          szTemp = "*** Multi-value Multi-type ***";
          break;

       case EA_MVST:
          szTemp = "*** Multi-value Single-type ***";
          break;

       default:
          szTemp = "*** Unknown EA type ***";
          break;

    }
    GetMem(szRet,strlen(szTemp)+1); /* Copy string from static to dynamic */
    strcpy(szRet,szTemp);
    return(szRet);
 }


 /*
  * Function name: MultiAdd()
  *
  * Parameters:  hwnd is the current window handle.
  *              pFEA points to the current FEA.
  *              pPDat gives the current m-m data.
  *
  * Returns: VOID.  Modifies the current pFEA.
  *
  * Purpose: This routine is called by MultiTypeProc and handles the addition
  *          of a subvalue to a multi-value, multi-type EA.
  *
  * Usage/Warnings:  NOTE:  Not all GetMem's are totally error trapped.
  *                  It is also possible that the add to the listbox could fai
  *
  * Calls: AddEAProc() (thru PM), MultiTypeIndex()
  *
  */

 VOID MultiAdd(HWND hwnd, HOLDFEA *pFEA,PASSDATA FAR *pPDat)
 {
    USHORT   usSize;
    USHORT   *pusPtr;
    CHAR     aUtility[6];      /* Used to hold the header for all EA types */
    CHAR     *pInsert,*pValue;
    PASSDATA PDat;

    PDat.Point = pFEA->szName;

    if(!WinDlgBox(HWND_DESKTOP,        /* Get the name and type */
                  hwnd,
                  AddEAProc,
                  (HMODULE) NULL,
                  IDD_ADDEA,
                  &PDat))
       return;                         /* They said cancel */

    pusPtr = (USHORT *) aUtility;
    *pusPtr= ConvTable[usRetEAType].usPrefix;   /* Set the type in header buf

    switch(ConvTable[usRetEAType].usFldType)
    {
       case IDD_ASCIIZ:     /* make buffer look like: xx FF 00, size 3 */
          usSize = 3;
          aUtility[2]=0;
          break;

       case IDD_LPDATA:     /* make the buffer look like: xx FF 00 00, size 4
          usSize = 4;
          pusPtr = (USHORT *) &aUtility[2];
          *pusPtr= 0;
          break;

       case IDD_MULTILIST:
          usSize = 6;
          pusPtr = (USHORT *) &aUtility[2];
          *pusPtr= 0; /* Zero out codepage */
          pusPtr++;
          *pusPtr= 0; /* Zero out fld cnt */
          break;
    }
    /* Increase EA size to accomodate the header */
    ResizeMem(pFEA->aValue,pFEA->cbValue,pFEA->cbValue+usSize);

    pusPtr  = (USHORT *) ((CHAR *) pFEA->aValue + pPDat->usMultiOffset);
    pusPtr+=2;    /* Point to the current number of m-m fields */

    /* Get ptr to beginning of current EA, scoot the rest down and insert
       the 3-4 byte header at the end of the list.                        */
    pInsert = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset, *pusPtr);
    memmove(pInsert+usSize,pInsert, pFEA->cbValue-(pInsert-pFEA->aValue));
    memcpy(pInsert,aUtility,usSize);

    pFEA->cbValue += usSize;   /* Fix up the counts */
    pPDat->cbMulti+= usSize;
                                                   /* Set the PDat for call */
    PDat.Point         = (CHAR *) pFEA;
    PDat.cbMulti       = pPDat->cbMulti;
    PDat.usMultiOffset = pPDat->usMultiOffset;
    PDat.usIndex       = *pusPtr;

    if(!EditEAValue(hwnd,&PDat)) /* They canceled the edit */
    {  /* Move the EA's back to effectively kill the inserted header */
       memmove(pInsert,pInsert+usSize,pFEA->cbValue-(pInsert-pFEA->aValue));
       ResizeMem(pFEA->aValue,pFEA->cbValue,pFEA->cbValue-usSize);
       pFEA->cbValue -= usSize;   /* Adjust counters */
       pPDat->cbMulti-= usSize;

       return;
    }

    /* Reset pusPtr since EditEAValue could have moved the base address */
    pusPtr  = (USHORT *) ((CHAR *) pFEA->aValue + pPDat->usMultiOffset);
    pusPtr+=2;

    pInsert = MultiTypeIndex(pFEA->aValue+pPDat->usMultiOffset, *pusPtr);

    *pusPtr += 1; /* Field cnt incremented AFTER call to Edit */

    pValue = EAValueString(hwnd,pInsert);   /* Add new field to the list box *

    WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_INSERTITEM,
                      MPFROM2SHORT(LIT_END,0),
                      MPFROMP(pValue));
    FreeMem(pValue,strlen(pValue)+1);

    FILE_CHANGED = TRUE;
 }



 NEADFILE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\NEAD\NEADFILE.C

 /*************************************************************

  This module contains subroutines for nead.c that specifically
  deal with the Open file interface.  Most of the routines were
  taken from Charles Petzold's book "Programming the OS/2
  Presentation Manager" and were slightly modified.

  Procedures in this file:
    OpenFile()          Asks user for new file name and opens it
    OpenFileProc()      Dialog proc that prompts user for file name
    FillDirListBox()    Fills the directory list box
    FillFileListBox()   Fills the file list box

 **************************************************************/
 #include "nead.h"

 /************ External GLOBALS *******************************/

 extern CHAR szFileName[CCHMAXPATH];
 extern CHAR szEAName[MAXEANAME+1];
 extern USHORT usRetEAType;
 extern BOOL FILE_ISOPEN;
 extern BOOL FILE_CHANGED;
 extern BOOL COMMAND_LINE_FILE;
 extern HHEAP hhp;
 extern CHAR *pAlloc,*szEditBuf,*szAscii,*szScratch;
 extern HOLDFEA *pHoldFEA;
 extern DELETELIST *pDelList;
 extern EADATA ConvTable[EATABLESIZE];

 /*************************************************************/


 /*
  * Function name: OpenFile()
  *
  * Parameters:  hwnd which is the current window handle.
  *              usMode which will attempt to load the file from the command
  *              line iff usMode == ARGFILE which is set in main().  Otherwise
  *              the selector box is brought up for the user to select from.
  *
  * Returns: TRUE iff a file is open upon exit.
  *
  * Purpose: This routine handles opening a new file.  It will also query the
  *          user for the disposition of the current file if it has been
  *          modified before loading the new file.
  *
  * Usage/Warnings:  Routine should be bullet proof as it does its own
  *                  error checking.  It assumes that hwnd points to the
  *                  correct window with the name listbox in it.
  *
  * Calls: WriteEAs(), Free_FEAList()
  */

 BOOL OpenFile(HWND hwnd,USHORT usMode)
 {
    CHAR szOldFile[CCHMAXPATH];
    USHORT usRet;

    strcpy(szOldFile,szFileName);  /* Save name of the currently open file */

    if(usMode != ARGFILE)          /* It isn't the command line file */
    {
       if(!WinDlgBox(HWND_DESKTOP, /* Get the file name from the user */
                     hwnd,
                     OpenFileProc,
                     (HMODULE) NULL,
                     IDD_OPENBOX,
                     NULL))
       {
          strcpy(szFileName,szOldFile); /* They canceled, restore old file */
          return(FILE_ISOPEN);
       }
    }

    if(FILE_CHANGED) /* Give them a chance to save modifications */
    {
       usRet=WinMessageBox(HWND_DESKTOP,hwnd,
                           "The current file has been changed.  Do you \
 wish to save the changes before proceeding?",
                           "Warning",0,MB_YESNOCANCEL | MB_ICONQUESTION);
       switch(usRet)
       {
          case MBID_YES:
             WriteEAs(hwnd);
             break;
          case MBID_CANCEL:
             return FILE_ISOPEN;
       }
    }

    if(FILE_ISOPEN) /* Free up everything associated with the current file */
    {
       Free_FEAList(pHoldFEA,pDelList);
       FILE_ISOPEN = FALSE;
    }

    if(QueryEAs(hwnd,szFileName)) /* We were successful */
    {
       HOLDFEA *pFEA=pHoldFEA;

       FILE_ISOPEN = TRUE;
       FILE_CHANGED = FALSE;

       WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_DELETEALL,0L,0L); /* Fill L-box */

       while(pFEA)
       {
          WinSendDlgItemMsg(hwnd, IDD_LBOX, LM_INSERTITEM,
                            MPFROM2SHORT(LIT_END,0),
                            MPFROMP(pFEA->szName));
          pFEA = pFEA->next;
       }
    }
    else /* We couldn't query the EAs */
    {
       *szFileName = '\000';
       WinSetDlgItemText(hwnd,IDD_FNAME,szFileName);
       return(FILE_ISOPEN = FALSE);
    }
    WinSetDlgItemText(hwnd,IDD_FNAME,szFileName);
    pDelList = NULL;
    return(TRUE);
 }


 /*
  * Function name: OpenFileProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              Expects no user pointer.
  *
  * Returns: TRUE if user selects OK, FALSE if Cancel is selected.
  *
  * Purpose: This proc handles the user interface to select a file name.
  *          Some elementary checks are done to make sure the filename is
  *          valid.
  *
  * Usage/Warnings:  The interface is NOT foolproof as it is possible to
  *                  continue with a non-existant file name.  Also, users
  *                  are not currently allowed to view/edit the EAs attached
  *                  to a directory.
  *
  * Calls: FillDirListBox(), FillFileListBox(), ParseFileName()
  */

 MRESULT EXPENTRY OpenFileProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    static CHAR szCurrentPath[CCHMAXPATH],szBuffer[CCHMAXPATH];
    CHAR        szParsedPath[CCHMAXPATH];
    SHORT       sSelect;

    switch(msg)
    {
       case WM_INITDLG:
          FillDirListBox(hwnd,szCurrentPath);
          FillFileListBox(hwnd);
          WinSendDlgItemMsg(hwnd, IDD_FILEEDIT,EM_SETTEXTLIMIT,
                                  MPFROM2SHORT(260,0),NULL);
          return 0L;

       case WM_CONTROL:
          if(SHORT1FROMMP(mp1) == IDD_DIRLIST ||  /* An lbox item is selected
             SHORT1FROMMP(mp1) == IDD_FILELIST)
          {
             sSelect = SHORT1FROMMR( WinSendDlgItemMsg(hwnd, /* Get item->szBu
                                SHORT1FROMMP(mp1),
                                LM_QUERYSELECTION, 0L, 0L));

             WinSendDlgItemMsg(hwnd, SHORT1FROMMP(mp1),
                               LM_QUERYITEMTEXT,
                               MPFROM2SHORT(sSelect, sizeof szBuffer),
                               MPFROMP(szBuffer));
          }

          switch(SHORT1FROMMP(mp1))
          {
             case IDD_DIRLIST:              /* Item was in the directory lbox
                switch(SHORT2FROMMP(mp1))
                {
                   case LN_ENTER:           /* Go to the select drive/dir */
                      if(*szBuffer == ' ')
                         DosSelectDisk(*(szBuffer+1) - '@');
                      else
                         DosChDir(szBuffer, 0L);

                      FillDirListBox(hwnd, szCurrentPath);
                      FillFileListBox(hwnd);

                      WinSetDlgItemText(hwnd, IDD_FILEEDIT, "");
                      return 0L;
                }
                break;

             case IDD_FILELIST:             /* Item was in the file lbox */
                switch(SHORT2FROMMP(mp1))
                {
                   case LN_SELECT:          /* Copy name to entry field  */
                      WinSetDlgItemText(hwnd, IDD_FILEEDIT, szBuffer);
                      return 0L;

                   case LN_ENTER:           /* Try to query the file */
                      if(ParseFileName(szFileName, szBuffer) != FILE_VALID)
                         return 0; /* Some error, don't finish */
                      WinDismissDlg(hwnd, TRUE);
                      return 0L;
                }
                break;
          }
          break;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:            /* Try to query file in the entry field *
                WinQueryDlgItemText(hwnd, IDD_FILEEDIT,
                                    sizeof szBuffer, szBuffer);

                switch(ParseFileName(szParsedPath, szBuffer))
                {
                   case FILE_INVALID:    /* Can't open the file */
                      WinAlarm(HWND_DESKTOP, WA_ERROR);
                      FillDirListBox(hwnd, szCurrentPath);
                      FillFileListBox(hwnd);
                      return 0L;

                   case FILE_PATH:    /* It was an incomplete path name */
                      strcpy(szCurrentPath,szBuffer);
                      FillDirListBox(hwnd, szCurrentPath);
                      FillFileListBox(hwnd);
                      WinSetDlgItemText(hwnd, IDD_FILEEDIT, "");
                      return 0L;

                   case FILE_VALID:    /* It was valid */
                      strcpy(szFileName, szParsedPath);
                      WinDismissDlg(hwnd, TRUE);
                      return 0L;
                }
                break;

             case DID_CANCEL:
                WinDismissDlg(hwnd, FALSE);
                return 0L;
          }
          break;
    }
    return WinDefDlgProc(hwnd, msg, mp1, mp2);
 }


 /*
  * Function name: FillDirListBox()
  *
  * Parameters:  hwnd points to the current window handle
  *              pcCurrentPath points to a buffer which will be filled in
  *              with the current path.
  *
  * Returns: VOID
  *
  * Purpose: This routine is called by OpenFileProc to fill in the directory
  *          list box
  *
  * Usage/Warnings:  Adequete error checking is NOT done on the return
  *                  values of the system calls.  Also, it is remotely
  *                  possible that the calls to add to the list box could fail
  *
  * Calls:
  */

 VOID FillDirListBox(HWND hwnd, CHAR *pcCurrentPath)
 {
    static CHAR szDrive [] = "  :";
    FILEFINDBUF findbuf;
    HDIR        hDir = 1;
    SHORT       sDrive;
    USHORT      usDriveNum, usCurPathLen, usSearchCount = 1;
    ULONG       ulDriveMap;

    DosQCurDisk(&usDriveNum, &ulDriveMap);
    *pcCurrentPath     = (CHAR)((CHAR) usDriveNum + '@');
    *(pcCurrentPath+1) = ':';
    *(pcCurrentPath+2) = '\\';
    usCurPathLen = CCHMAXPATH;
    DosQCurDir(0, pcCurrentPath + 3, &usCurPathLen);

    WinSetDlgItemText(hwnd, IDD_PATH, pcCurrentPath);
    WinSendDlgItemMsg(hwnd, IDD_DIRLIST, LM_DELETEALL, NULL, NULL);

    for(sDrive = ('A'-'A'); sDrive <= ('Z'-'A'); sDrive++)
    {
       if(ulDriveMap & (1L << sDrive))
       {
          *(szDrive+1) = (CHAR)((CHAR) sDrive + 'A');

          WinSendDlgItemMsg(hwnd, IDD_DIRLIST, LM_INSERTITEM,
                            MPFROM2SHORT(LIT_END, 0),
                            MPFROMP(szDrive));
       }
    }
    DosFindFirst("*", &hDir, FILE_DIRECTORY | FILE_ALL, &findbuf,
                 sizeof findbuf, &usSearchCount, 0L);
    while(usSearchCount)
    {
       if((findbuf.attrFile & FILE_DIRECTORY) &&
          (findbuf.achName[0] != '.' || findbuf.achName[1]))

          WinSendDlgItemMsg(hwnd, IDD_DIRLIST, LM_INSERTITEM,
                            MPFROM2SHORT(LIT_SORTASCENDING, 0),
                            MPFROMP(findbuf.achName));

       if(DosFindNext(hDir, &findbuf, sizeof findbuf, &usSearchCount))
          break;
    }
 }


 /* This routine is called by OpenFileProc to fill the file list box */

 /*
  * Function name: FillFileListBox()
  *
  * Parameters:  hwnd points to the current window handle
  *
  * Returns: VOID
  *
  * Purpose: This routine is called by OpenFileProc to fill in the file
  *          list box with files in the current directory.
  *
  * Usage/Warnings:  Adequete error checking is NOT done on the return
  *                  values of the system calls.  Also, it is remotely
  *                  possible that the calls to add to the list box could fail
  *
  * Calls:
  */

 VOID FillFileListBox(HWND hwnd)
 {
    FILEFINDBUF findbuf;
    HDIR        hDir = 1;
    USHORT      usSearchCount = 1; /* Read 1 entry at a time */

    WinSendDlgItemMsg(hwnd, IDD_FILELIST, LM_DELETEALL, NULL, NULL);

    DosFindFirst("*", &hDir, FILE_ALL, &findbuf, sizeof findbuf,
                        &usSearchCount, 0L);
    while(usSearchCount)
    {
       WinSendDlgItemMsg(hwnd, IDD_FILELIST, LM_INSERTITEM,
                         MPFROM2SHORT(LIT_SORTASCENDING, 0),
                         MPFROMP(findbuf.achName));

       if(DosFindNext(hDir, &findbuf, sizeof findbuf, &usSearchCount))
          break;
    }
 }


 /*
  * Function name: ParseFileName()
  *
  * Parameters:  pcOut points to a buffer for the return file specification.
  *              pcIn  points to the buffer containing the raw input spec.
  *
  * Returns: FILE_INVALID if pcIn had invalid drive or no directory
  *          FILE_PATH    if pcIn was empty or had just a path/no file name.
  *          FILE_VALID   if pcIn point to good file.
  *
  * Purpose: This routine changes drive and directory as per pcIn string.
  *
  * Usage/Warnings:  Note that pcOut is only valid if FILE_VALID is returned.
  *                  in place of strupr(), a codepage should be fetched and
  *                  DosCaseMap() should be used to allow for extended chars.
  *                  This routine could use some cleanup work.
  *
  * Calls:
  */

 SHORT ParseFileName(CHAR *pcOut, CHAR *pcIn)
 {
    CHAR   *pcLastSlash, *pcFileOnly ;
    ULONG  ulDriveMap ;
    USHORT usDriveNum, usDirLen = CCHMAXPATH;

    strupr(pcIn);  /* Does NOT handle extended chars, should use DosCaseMap */

    if(*pcIn == '\000')  /* If string is empty, return FILE_PATH */

       return FILE_PATH;

    /* Get drive from input string or use current drive */

    if(*(pcIn+1) == ':') /* Yup, they specified a drive */
    {
       if(DosSelectDisk(*pcIn - '@')) /* Change to selected drive */
          return FILE_INVALID;
       pcIn += 2;
    }
    DosQCurDisk(&usDriveNum, &ulDriveMap); /* Get current drive */

    *pcOut++ = (CHAR)((CHAR) usDriveNum + '@'); /* Build drive letter */
    *pcOut++ = ':';
    *pcOut++ = '\\';

    if(*pcIn == '\000') /* If rest of the string is empty, return FILE_PATH */
       return FILE_PATH;

    /* Search for the last backslash.  If none, it could be a directory. */

    if(!(pcLastSlash = strrchr(pcIn, '\\'))) /* No slashes? */
    {
       if(!DosChDir(pcIn, 0L))
          return FILE_PATH;            /* It was a directory */

       DosQCurDir(0, pcOut, &usDirLen); /* Get current dir & attach input fn *

       if(*(pcOut+strlen(pcOut)-1) != '\\')
          strcat(pcOut++, "\\");

       strcat(pcOut, pcIn);
       return FILE_VALID;
    }

    /* If the only backslash is at the beginning, change to root */

    if(pcIn == pcLastSlash)
    {
       DosChDir("\\", 0L);

       if(*(pcIn+1) == '\000')
          return FILE_PATH;

       strcpy(pcOut, pcIn+1);
       return FILE_VALID;
    }

    /* Attempt to change directory -- Get current dir if OK */

    *pcLastSlash = 0;

    if(DosChDir(pcIn, 0L))
       return FILE_INVALID;

    DosQCurDir(0, pcOut, &usDirLen);

    /* Append input filename if any */

    pcFileOnly = pcLastSlash+1;

    if(*pcFileOnly == '\000')
       return FILE_PATH;

    if(*(pcOut+strlen(pcOut)-1) != '\\')
       strcat(pcOut++, "\\");

    strcat(pcOut, pcFileOnly);
    return FILE_VALID;
 }



 OPTIONS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TERMINAL\OPTIONS.C

 /***
  *
  * TITLE
  *
  *        options.c
  *        Created by Microsoft Corporation 1987
  *
  * DESCRIPTION
  *
  *        This module handles the user interface for setting the options for
  *      the COM port and the MODEM.
  *
  *         If a filename was specified in the command line, the initialisatio
  *        of the COM port and the MODEM will be performed as indicated in
  *        the file (creation of this file will be described later). If an err
  *        is encountered in opening the file or, if no file was specified in
  *        command line, an option screen is displayed. The user is then requi
  *        to specify the appropriate options for initialisation of the COM po
  *        and the MODEM.
  *
  *         The program will request the following information from the user v
  *        the option screen:
  *
  *                Port        :
  *                Baud Rate   :
  *                Data Bits   :
  *                Stop Bits   :
  *                Parity      :
  *                Connect     :
  *                Dial Type   :
  *                Wait Tone   :
  *                Wait Carrier:
  *                Phone Number:
  *                Setup File  :
  *
  *        Port:
  *                Specify com port. The available options are com1, com2.
  *        Baud Rate:
  *                The available options are 1200, 2400, 4800, 9600, 110,
  *                150, 300, 600.
  *        Data Bits:
  *                The available options are 7 and 8.
  *        Stop Bits:
  *                The available options are 1 and 2.
  *        Parity:
  *                The available options are even, none and odd.
  *        Connect:
  *                The available options are computer and modem.
  *        Dial Type:
  *                The available options are tone and pulse.
  *        Wait Tone:
  *                The available options are 10, 15, 20, 25, 50, 75, 100,
  *                125, 150, 175, 200, 225 and 250 seconds.
  *                Determines how long the modem waits after "picking up
  *                the telephone" before it dials the first digit of the
  *                telephone number. This delay allows time for the central
  *                office to detect the "off-hook" condition of the line and
  *                apply a dial tone.
  *        Wait Carrier:
  *                The available options are 10, 15, 20, 25, and 30.
  *                Sets the time that the local modem waits for carrier from
  *                the remote modem before hanging up.
  *        Phone Number:
  *                Type the telephone number of the service. Type the entire
  *                number including 1 and the area code if necessary.
  *                Commas instruct the modem to pause before dialing a number.
  *                For example, if you must dial 9 to get an outside line, typ
  *                a comma after the 9. This allows time to wait for a dial
  *                tone before the modem dials the number. You can use hyphens
  *                in the number for clarity; the modem ignores them.
  *        Setup File:
  *                This is the name of file in which the options will be saved
  *                The default file name is terminal.ini . If you prefer
  *              a different name, delete the default name and type the
  *              name you prefer. You may include the path name.
  *
  */

 #define     INCL_DOSSIGNALS
 #define     INCL_SUB

 #include        <os2def.h>
 #include        <bse.h>
 #include        <stdio.h>
 #include        <malloc.h>
 #include        <string.h>
 #include         <memory.h>
 #include         <conio.h>
 #include         "term.h"

 extern void far error (int, int);

 extern char         *ErrMsg[NUM_MSG];

 #define Z_ALPHA     0x01
 #define Z_DIGIT     0x02
 #define Z_WHITE     0x04
 #define Z_PATH            0x08
 #define Z_PHONE     0x10

 #define     O_WIDTH        62
 #define     O_HEIGHT        21

 char optScreen[O_HEIGHT][O_WIDTH+1] = {
    /* 123456789 123456789 123456789 123456789 123456789 123456789 1 */
     "                    [ Terminal Parameters ]                   ", /*  0 *
     "                                                              ", /*  1 *
     "   Port        :     com1                                     ", /*  2 *
     "   Baud Rate   :     1200                                     ", /*  3 *
     "   Data Bits   :        7                                     ", /*  4 *
     "   Stop Bits   :        2                                     ", /*  5 *
     "   Parity      :     even                                     ", /*  6 *
     "   Connect     : computer                                     ", /*  7 *
     "   Dial Type   :     tone                                     ", /*  8 *
     "   Wait Tone   :       30                                     ", /*  9 *
     "   Wait Carrier:       10                                     ", /* 10 *
     "                                                              ", /* 11 *
     "   Phone Number: 9,555-1212                                   ", /* 12 *
     "                                                              ", /* 13 *
     "   Setup File  : terminal.ini                                 ", /* 14 *
     "                                                              ", /* 15 *
     "                                                              ", /* 16 *
     "       Use UP and DOWN cursor keys to move between fields.    ", /* 17 *
     "       Use LEFT and RIGHT cursor keys to change a field.      ", /* 18 *
     "                                                              ", /* 19 *
     "                   [ Press Enter to return ]                  "  /* 20 *
 };

 #define     rowPort        2
 #define     colPort    21

 #define     rowBaud        3
 #define     colBaud    21

 #define     rowData        4
 #define     colData    24

 #define     rowStop        5
 #define     colStop    24

 #define     rowParity        6
 #define     colParity  21

 #define            rowConnect  7
 #define     colConnect 17

 #define     rowDial     8
 #define     colDial    20

 #define     rowTone     9
 #define     colTone    22

 #define     rowCarrier  10
 #define     colCarrier  23

 #define     rowPhone   12
 #define     colPhone   17
     wPhone     20                /* width of phone string */
 #define     sPhone  (Z_DIGIT | Z_PHONE)
                          /*012345678901234567890*/
 char strPhone[wPhone+1] = "9,555-1212           ";
 #define     lenPhone   12

 #define     rowFile    14
 #define     colFile    17
 #define     wFile      40
 #define     sFile   (Z_PATH | Z_ALPHA | Z_DIGIT)
                      /*01234567890123456789012345678901234567890*/
 char strFile[wFile+1]="terminal.ini                             ";
 #define     lenFile    13

 int O_ROW;        /* Coordinates of top left corner of option menu */
 int O_COL;

 char *csBaud[] = {
     "1200",
     "2400",
     "4800",
     "9600",
     " 110",
     " 150",
     " 300",
     " 600"
 };

 #define nBaud        (sizeof(csBaud)/sizeof(char*))
 #define wBaud   4
 int intBaud[nBaud] = {
    1200,
    2400,
    4800,
    9600,
     110,
     150,
     300,
     600
 };

 char *csPort[] = {
     "com1",
     "com2"
 };
 #define nPort        (sizeof(csPort)/sizeof(char*))
 #define wPort        4

 char *csData[] = {
     "7",
     "8"
 };
 #define nData        (sizeof(csData)/sizeof(char*))
 #define wData        1

 UCHAR chData[nData] = {
     7,
     8
 };

 NPCH csStop[] = {
     "1",
     "2"
 };
 #define nStop        (sizeof(csStop)/sizeof(char*))
 #define wStop        1

 UCHAR chStop[nStop] = {
     0,
     2
 };

 NPCH csParity[] = {
     "even",
     " odd",
     "none"
 };
 #define nParity   (sizeof(csParity)/sizeof(char*))
 #define wParity   4

 UCHAR chParity[nParity] = {
     EVEN,
     ODD,
     NONE
 };

 NPCH csConnect[] = {
     "computer",
     "   modem"
 };
 #define nConnect   (sizeof(csConnect)/sizeof(char*))
 #define wConnect   8
 UCHAR chConnect[nConnect] = {
     COMPUTER,
     MODEM
 };

 NPCH csDial[] = {
     " tone",
     "pulse"
 };
 #define nDial   (sizeof(csDial)/sizeof(char*))
 #define wDial   5
 UCHAR chDial[nConnect] = {
     TONE,
     PULSE
 };

 NPCH csTone[] = {
     " 10",
     " 15",
     " 20",
     " 25",
     " 50",
     " 75",
     "100",
     "125",
     "150",
     "175",
     "200",
     "225",
     "250"
 };
 /* register S6 in Hayes Modem */
 #define nTone   (sizeof(csTone)/sizeof(char*))
 #define wTone   3
 int iTone[nTone] = {10,15,20,25,50,75,100,125,150,175,200,225,250};

 NPCH csCarrier[] = {
     "30",
     "25",
     "20",
     "15",
     "10",
     " 5"
 };
 /* register S7 in Hayes Modem */
 #define nCarrier   (sizeof(csCarrier)/sizeof(char*))
 #define wCarrier   2
 int iCarrier[nCarrier] = {30, 25, 20, 15, 10, 5};

 OT_SET            0                /* Option types */
 #define OT_ASCIIZ   1

 UCHAR cMap[256];

 typedef struct {
     int sc_n;                        /* number of different values */
     char **sc_name;                /* array of value names */
 } setControl_s;

 typedef struct {
     UCHAR az_Set;        /* flags of valid characters */
 } azControl_s;

 typedef struct {
     int ctl_type;                /* Type of control */
     int ctl_row;                /* row to display value name */
     int ctl_col;                /* column to display value name */
     int ctl_width;                /* width of value names */
     union {
         setControl_s  sc;
         azControl_s   az;
     } ctl_union;
 } control_s;

 typedef union {
     struct {
         char *oaz_str;
         int   oaz_cur;                /* cursor position */
     } oaz;
     int   osc;
 } option_u;

 control_s  control[] = {
     {OT_SET   ,rowPort          ,colPort    ,wPort    ,{nPort    ,csPort    }
     {OT_SET   ,rowBaud          ,colBaud    ,wBaud    ,{nBaud    ,csBaud    }
     {OT_SET   ,rowData          ,colData    ,wData    ,{nData    ,csData    }
     {OT_SET   ,rowStop          ,colStop    ,wStop    ,{nStop    ,csStop    }
     {OT_SET   ,rowParity  ,colParity  ,wParity  ,{nParity  ,csParity  }},
     {OT_SET   ,rowConnect ,colConnect ,wConnect ,{nConnect ,csConnect }},
     {OT_SET   ,rowDial    ,colDial    ,wDial    ,{nDial    ,csDial    }},
     {OT_SET   ,rowTone    ,colTone    ,wTone    ,{nTone    ,csTone    }},
     {OT_SET   ,rowCarrier ,colCarrier ,wCarrier ,{nCarrier ,csCarrier }},
     {OT_ASCIIZ,rowPhone   ,colPhone   ,wPhone   ,{sPhone              }},
     {OT_ASCIIZ,rowFile          ,colFile    ,wFile    ,{sFile
 };

 option_u option[] = {                 /* Current option settings */
         {0},                        /* port        */
         {0},                        /* baud        */
         {0},                        /* data bits   */
         {0},                        /* stop bits   */
         {0},                        /* parity      */
         {0},                        /* connect     */
         {0},                        /* dial type   */
         {0},                        /* wait tone   */
         {0},                        /* wait carrier*/
         {strPhone,0},                /* phone       */
         {strFile ,0}                /* file        */
 };
 #define nOption    (sizeof(option)/sizeof(option_u))

 static char         AttrPanel  = COLOR_PANEL,
                      AttrOption = COLOR_OPTION,
                       AttrCursor = COLOR_CURSOR;
 static int        N_of_Cols,                /* number of columns on screen */
                 N_of_Rows;                /* number of rows on screen */




 /***         get_options - get COM port settings and modem settings
  *
  *        This routine determines the type of display and sets the display
  *        attributes appropriately. If a filename was specified in the comman
  *        line, it opens the file and displays the COM port and the MODEM opt
  *        from the file. These options may be modified but the modifications
  *        not be written out to the file. If the file open failed, or, if no
  *        was specified in the command line, this routine wil display the def
  *        options on the screen. The user may modify the options and it will
  *        writen out to the file specified in the options screen.
  *
  *        get_options(argc,argv)
  *
  *        ENTRY
  *                argc - number of command line arguments
  *                argv - pointer to an array of pointers (to command line arg
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void get_options(argc, argv)
 int         argc;
 char         *argv[];
 {
         FILE                *fp;
         char                OptionsSet = FALSE; /* indicate if options are se
         unsigned        RetCode;
         static VIOCONFIGINFO DispConfigData = {sizeof(DispConfigData),};

         /* if the display is monochrome, change the attributes */
         if ((RetCode = VioGetConfig(RESERVED,&DispConfigData,RESERVED)) != 0)
           error(ERR_VIOGETCONFIG, RetCode);
         if ((DispConfigData.adapter == MONOCHROME) ||
             (DispConfigData.display == MONOCHROME)) {
           AttrPanel  = MONO_PANEL;
           AttrOption = MONO_OPTION;
           AttrCursor = MONO_CURSOR;
         }


         if (argc > 1) {          /* get com/modem settings from the file */
           if ((fp = fopen(argv[1], "r")) != NULL) {

             /* clear the phone-number and file-name string buffers */
             strnset(strPhone, ' ', wPhone+1);
             strnset(strFile, ' ', sFile+1);

             /* read the option settings from the file */
             fscanf(fp, "%d %d %d %d %d %d %d %d %d \n",
                    &option[PORT_NAME].osc, &option[BAUD_RATE].osc,
                    &option[DATA_BITS].osc, &option[STOP_BITS].osc,
                    &option[PARITY].osc, &option[CONNECT].osc,
                    &option[DIAL_TYPE].osc, &option[WAIT_TONE].osc,
                    &option[WAIT_CARRIER].osc);
             fscanf(fp, "%21s \n", option[PHONE_NUMBER].oaz.oaz_str);
             fscanf(fp, "%41s \n", option[SETUP_FILE].oaz.oaz_str);
             printf("file name = %s \n", option[SETUP_FILE].oaz.oaz_str);
             option[PHONE_NUMBER].oaz.oaz_cur = 0;  /* init cursor position */
             option[SETUP_FILE].oaz.oaz_cur = 0;    /* init cursor position */

             /* show the options on the screen */
                 screen_init();                /* init the physical screen */
                 option_init();
                 show_option();

             OptionsSet = TRUE;
             fclose(fp);
           }
         }

         if (!OptionsSet) {                /* get options from the user */
               screen_init();                        /* init the physical scre
               option_init();
               show_option();
           /* write option settings to the file specified. */
           if ((fp = fopen(option[SETUP_FILE].oaz.oaz_str, "w")) != NULL) {
             DosHoldSignal(HLDSIG_DISABLE);
             fprintf(fp, "%d %d %d %d %d %d %d %d %d \n",
                     option[PORT_NAME].osc, option[BAUD_RATE].osc,
                     option[DATA_BITS].osc, option[STOP_BITS].osc,
                     option[PARITY].osc, option[CONNECT].osc,
                     option[DIAL_TYPE].osc, option[WAIT_TONE].osc,
                     option[WAIT_CARRIER].osc);
             fprintf(fp, "%21s \n", option[PHONE_NUMBER].oaz.oaz_str);
             fprintf(fp, "%41s \n", option[SETUP_FILE].oaz.oaz_str);
             fclose(fp);
             DosHoldSignal(HLDSIG_ENABLE);}
         }
 }




 /***        get_com_options - get Com port options
  *
  *        This routine copies the current com port options into a structure
  *        provided by the caller.
  *
  *        get_com_options(psComOptions)
  *
  *        ENTRY
  *                psComOptions = ptr to structure of com port options
  *
  *        EXIT
  *                the structure pointed by psComOptions is filled with com
  *                port options
  *
  *        WARNING
  *
  *        EFFECTS
  *
  */

 void get_com_options(psComOptions)
 structComOptions *psComOptions;                /* ptr to struct of com port o
 {
         psComOptions->pPortName  = csPort[(option[PORT_NAME].osc)];
         psComOptions->iBaudRate  = intBaud[(option[BAUD_RATE].osc)];
         psComOptions->chDataBits = chData[(option[DATA_BITS].osc)];
         psComOptions->chStopBits = chStop[(option[STOP_BITS].osc)];
         psComOptions->chParity   = chParity[(option[PARITY].osc)];
 }




 /***        get_modem_options - get modem options
  *
  *        This routine copies the current modem options into a structure
  *        provided by the caller.
  *
  *        get_modem_options(psModemOptions)
  *
  *        ENTRY
  *                psModemOptions = ptr to structure of modem options
  *
  *        EXIT
  *                the structure pointed by psModemOptions is filled with
  *                modem options
  *
  *        WARNING
  *
  *        EFFECTS
  *
  */

 void get_modem_options(psModemOptions)
 structModemOptions *psModemOptions;        /* ptr to struct of modem options
 {
         psModemOptions->chDialType   = chDial[(option[DIAL_TYPE].osc)];
         psModemOptions->iWaitTone    = iTone[(option[WAIT_TONE].osc)];
         psModemOptions->iWaitCarrier = iCarrier[(option[WAIT_CARRIER].osc)];
         psModemOptions->pPhoneNumber = option[PHONE_NUMBER].oaz.oaz_str;
 }




 /***        modem - determines if modem connection was requested
  *
  *        modem()
  *
  *        ENTRY
  *
  *        EXIT
  *                modem = TRUE if modem connection was requested
  *                        FALSE otherwise
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 modem()
 {
         return (chConnect[(option[CONNECT].osc)] == MODEM);
 }




 /***        clear_screen - blank out the entire screen
  *
  *        clear_screen()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 clear_screen()
 {
     Cell                 c;
     int                        RetCode;

     c.ch = ' ';
     c.at = ATTR(WHITE,BLACK);
     if ((RetCode = VioWrtNCell((PBYTE)(&c), N_of_Rows*N_of_Cols,
                                0, 0, RESERVED)) != 0)
       error(ERR_VIOWRTNCELL, RetCode);
 }   /* clear_screen */




 /***        option_init - initialize option screen
  *
  *        option_init()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 option_init()
 {
             int                 i;

             O_ROW = (N_of_Rows - O_HEIGHT)/2;
             O_COL = (N_of_Cols - O_WIDTH)/2;
             for (i=0; i<nOption; i++) {   /* adjust to real screen coordinate
           control[i].ctl_row += O_ROW;
           control[i].ctl_col += O_COL;
             }
             init_cmap();
 }           /* option_init */




 /***        init_cmap - initialise valid character set
  *
  *        init_cmap()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 init_cmap()
 {
             register int         i;

             for (i=0; i<256; i++)
           cMap[i] = 0;
             for (i='a'; i<='z'; i++)
           cMap[i] |= Z_ALPHA;
             for (i='A'; i<='Z'; i++)
           cMap[i] |= Z_ALPHA;
             for (i='0'; i<='9'; i++)
           cMap[i] |= Z_DIGIT;
             cMap[':']  |= Z_PATH;
             cMap['\\'] |= Z_PATH;
             cMap['/']  |= Z_PATH;
             cMap['.']  |= Z_PATH;
             cMap['-']  |= Z_PATH;
             cMap[' ']  |= Z_WHITE;
             cMap['\t'] |= Z_WHITE;
             cMap['-']  |= Z_PHONE;
             cMap[',']  |= Z_PHONE;
 }           /* init_cmap */




 /***        show_option - display option screen and record user modifications
  *
  *        show_option()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 show_option()
 {
         int                RetCode;

             do_option();
         clear_screen();
         if ((RetCode = VioSetCurPos(0, 0, RESERVED)) != 0)
           error(ERR_VIOSETCURPOS, RetCode);
 }           /* show_option */




 /***        do_option - show and update option screen
  *
  *        do_option()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 do_option()
 {
         USHORT                row, col;
         BYTE                a;
             int                 RetCode,
                         i,
                             key,
                             iOpt,lastOpt;

             a = AttrPanel;
             for (row=0; row<O_HEIGHT; row++)            /* display option pan
           if ((RetCode = VioWrtCharStrAtt(optScreen[row],O_WIDTH,row+O_ROW,
                                           O_COL, &a, RESERVED)) != 0)
             error(ERR_VIOWRTCHARSTRATT, RetCode);
             for (i=0; i<nOption; i++)           /* Fill in options */
           show_opt(i, AttrOption);

             key = 0;
             iOpt = 0;        /* Start with first option */
             lastOpt = iOpt;
             while (key != ENTER_KEY) {
           if (iOpt != lastOpt)            /* user moved to different field */
             show_opt(lastOpt, AttrOption); /* revert to normal color */
           show_opt(iOpt, AttrCursor);        /* highlight current field */
           lastOpt = iOpt;
           key = get_key();
           switch (key) {
             case UP_KEY:
                         if (--iOpt < 0)
                               iOpt = nOption - 1;
                         break;
             case DOWN_KEY:
                         if (++iOpt > (nOption - 1))
                               iOpt = 0;
                         break;
             case RIGHT_KEY:
                         mod_option(iOpt,  1);
                         break;
             case LEFT_KEY:
                         mod_option(iOpt, -1);
                         break;
             case DEL_KEY:
                         if (control[iOpt].ctl_type == OT_ASCIIZ)
                               do_right_del(iOpt);
                         break;
             case HOME_KEY:
                         if (control[iOpt].ctl_type == OT_ASCIIZ)
                               option[iOpt].oaz.oaz_cur = 0;
                         break;
             case BKSP_KEY:
                         if (control[iOpt].ctl_type == OT_ASCIIZ)
                               do_left_del(iOpt);
                         break;
             default:
                         switch (control[iOpt].ctl_type) {
                           case OT_SET:    /* Ignore other key strokes */
                                         break;
                           case OT_ASCIIZ:
                                         do_insert(iOpt,key);
                                         break;
                               default:
                                         error(ERR_DO_OPT_INVOPT, NO_RETCODE);
                         }
                         break;
           }           /* switch */
             }        /* while */
 }           /* do_option */




 /***        do_insert - insert character in a string
  *
  *        do_insert(iOpt,key)
  *
  *        ENTRY
  *                iOpt = which option (setup File or Phone Number)
  *                key  = character to insert
  *
  *        EXIT
  *                char inserted into string (setup File name or Phone Number)
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 do_insert(iOpt,key)
 int          iOpt;
 unsigned key;
 {
             int           ch;
             int           cur;
             int           i,n;
             int           width;
             char         *s,*d,*p;

             ch = key >> 8;
             if (ch > 0x7F)        /* Ignore */
           return(0);
             if ((cMap[ch] & control[iOpt].ctl_union.az.az_Set) == 0)
           return(0);         /* Not valid char */

             /*        oaz_cur - points to insertion point
                 We throw away characters at end of string.

                 before: aaaaaaabbbbbbbbbbbbc
                                ^
                 after:        aaaaaaaxbbbbbbbbbbbb
                                 ^
             */
             cur = option[iOpt].oaz.oaz_cur;
             width = control[iOpt].ctl_width;
             p = option[iOpt].oaz.oaz_str;

             d = p+width-1;                /* End of string */
             s = d-1;                /* End of string - 1 */
             n = (width-cur)-1;        /* Number of chars to shift */
             for (i=n; i>0; i--)
           *d-- = *s--;                /* Shift string */
             p[cur] = ch;                /* Insert character */
             cur++;                        /* Advance insertion point */
             if (cur > (width-1))    /* cursor wrapped */
           cur = 0;
             option[iOpt].oaz.oaz_cur = cur;
 }




 /***        do_left_del - delete a character which is to the left of the curs
  *
  *        do_left_del(iOpt)
  *
  *        ENTRY
  *                iOpt = which option (setup File or Phone Number)
  *
  *        EXIT
  *                char deleted from string (setup File name or Phone Number)
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 do_left_del(iOpt)
 int         iOpt;
 {
             if (option[iOpt].oaz.oaz_cur != 0)        /* Ignore at start of f
             /*
                 We delete the character to the left of the cursor and shift
                 rest of characters left 1.  Ignore at beginning of field.

                 before: aaaaaaaxbbbbbbbbbbbb
                                 ^
                 after:        aaaaaaabbbbbbbbbbbb
                                       ^
             */
             do_del(iOpt,0,-1);
 }




 /***        do_right_del - delete a character which is at the cursor
  *
  *         do_right_del(iOpt)
  *
  *        ENTRY
  *                iOpt = which option (setup File or Phone Number)
  *
  *        EXIT
  *                char deleted from string (setup File name or Phone Number)
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 do_right_del(iOpt)
 int         iOpt;
 {
             /*
                 We delete the character at the cursor and shift
                 rest of characters left 1.

                 before: aaaaaaaxbbbbbbbbbbbb
                                       ^
                 after:        aaaaaaabbbbbbbbbbbb
                                ^
             */
             do_del(iOpt,1,0);
 }




 /***        do_del - delete a character
  *
  *        do_del(iOpt,iShift,iCur)
  *
  *        ENTRY
  *                iOpt = which option (setup File or Phone Number)
  *                iShift = amount by which string should be shifted
  *                iCur = amount by which cursor should be moved
  *        EXIT
  *                char deleted from string (setup File name or Phone Number)
  *                cursor position within the string updated
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 do_del(iOpt,iShift,iCur)
 int         iOpt,
         iShift,
         iCur;
 {
             int         cur,width,i,n;
             char         *s,*d,*p;

             cur = option[iOpt].oaz.oaz_cur;
             width = control[iOpt].ctl_width;
             p = option[iOpt].oaz.oaz_str;

             s = p+cur+iShift;
             d = s-1;
             n = (width-(cur+iCur))-1;        /* Number of chars to shift */
             for (i=n; i>0; i--)
           *d++ = *s++;                        /* Shift string */
             cur += iCur;                        /* Adjust cursor */
             p[width-1] = ' ';                /* Put blank at end */
             option[iOpt].oaz.oaz_cur = cur;
 }




 /***        mod_option - modify option
  *
  *        mod_option(iOpt, inc)
  *
  *        ENTRY
  *                iOpt = which option (Port, Baud Rate, ..., setup File)
  *                inc  =  1 ( -> key was pressed)
  *                       -1 ( <- key was pressed)
  *
  *        EXIT
  *                option array entry 'osc' updated for all iOpt except for
  *                setupFile and PhoneNumber. In the case of setupFile and
  *                PhoneNumber, the cursor position is updated (cursor moved
  *                left if '<-' key was pressed; cursor moved right if '->'
  *                key was pressed)
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 mod_option(iOpt, inc)
 int         iOpt,
         inc;
 {
             int         col;
             int         width;

     switch (control[iOpt].ctl_type) {
         case OT_SET:        /* Ignore other key strokes */
             if (inc > 0) {  /* Increment value */
                 if (++option[iOpt].osc > (control[iOpt].ctl_union.sc.sc_n - 1
                     option[iOpt].osc = 0;
             }
             else            /* Decrement value */
                 if (--option[iOpt].osc < 0 )
                     option[iOpt].osc = control[iOpt].ctl_union.sc.sc_n - 1;
             break;
         case OT_ASCIIZ:
             col = option[iOpt].oaz.oaz_cur;
             width = control[iOpt].ctl_width;
             if (inc > 0) {  /* move cursor right */
                 if (++col > (width-1))
                     col = 0;
             }
             else            /* move cursor left */
                 if (--col < 0 )
                     col = (width-1);
             option[iOpt].oaz.oaz_cur = col;
             break;
         default:
             error(ERR_MOD_OPT_INVOPT, NO_RETCODE);
     }
 }




 /***        show_opt - show option
  *
  *        show_opt(iOpt, color)
  *
  *        ENTRY
  *                iOpt = which option (Port, Baud Rate, . . ., Phone Number)
  *                color = color in which the option is to be displayed
  *
  *        EXIT
  *                option indicated by iOpt is displayed on the screen
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 show_opt(iOpt, color)
 int        iOpt;
 char        color;
 {
         USHORT                row,col,width,
                         curCol;
             char                 *name;
             unsigned         RetCode;

             row = control[iOpt].ctl_row;
             col = control[iOpt].ctl_col;
             width = control[iOpt].ctl_width;
             switch (control[iOpt].ctl_type) {
           case OT_SET:
             name = control[iOpt].ctl_union.sc.sc_name[option[iOpt].osc];
             curCol = col+width-1;
             break;
           case OT_ASCIIZ:
             name = option[iOpt].oaz.oaz_str;
             curCol = col+option[iOpt].oaz.oaz_cur;
             break;
           default:
             error(ERR_SHOW_OPT_INVOPT, NO_RETCODE);
             };
         if ((RetCode = VioWrtCharStrAtt(name, width, row, col,
                                         &color, RESERVED)) != 0)
           error(ERR_VIOWRTCHARSTRATT, RetCode);
         if ((RetCode = VioSetCurPos(row, curCol, RESERVED)) != 0)
           error(ERR_VIOSETCURPOS, RetCode);
 }




 /***        get_key - return two-byte key stroke; includes IBM extended ASCII
  *
  *        get_key()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 get_key()
 {
     char         c;
     unsigned         key;

     if (c = getch())                    /* Get character */
       key = c << 8;                    /* Make scan code zero */
     else
       key = getch();                    /* Get scan code */
     return(key);
 }   /* get_key */




 /***        screen_init - Initialize the screen
  *
  *        screen_init()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 screen_init()
 {
         VIOMODEINFO TheModeData;    /* Mode data from VioGetMode */
         int            RetCode;

         if ((RetCode = VioGetMode(&TheModeData, RESERVED)) != 0)
           error(ERR_VIOGETMODE, RetCode);
             N_of_Cols = TheModeData.col;
             N_of_Rows = TheModeData.row;
         clear_screen();
 }           /* screen_init */


 OPTIONS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\OPTIONS.C

 /***************************************************************************\
 * options.c - Spy code for several options type dialogs
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define        INCL_WINBUTTONS
 #define        INCL_WINDIALOGS
        INCL_WINHEAP                /* needed for spy.h */
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMESSAGEMGR
        INCL_WINPOINTERS        /* needed for spy.h */
 #include <os2.h>
 #include "spyhook.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include "spy.h"

 /* Prototypes */
 MRESULT EXPENTRY SpyOutputsDlgProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY SpySaveListDlgProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY AboutWndProc(HWND, USHORT, MPARAM, MPARAM);

 /***************************************************************************\
 * MRESULT EXPENTRY SpyOutputsDlgProc (hwnd, msg, mp1, mp2)
 \***************************************************************************/
 MRESULT EXPENTRY SpyOutputsDlgProc(hwnd, msg, mp1, mp2)
 HWND            hwnd;
 USHORT          msg;
 MPARAM          mp1;
 MPARAM          mp2;
 {
     USHORT      wAction;

     switch (msg) {

     case WM_INITDLG:
         /*
          * Now initialize the output options in the dialog
          */
         WinSendDlgItemMsg(hwnd, DID_WINDOW, BM_SETCHECK,
             (MPARAM)spyopt.fWindow, 0L);
         WinSendDlgItemMsg(hwnd, DID_FILE, BM_SETCHECK,
             (MPARAM)spyopt.fFile, 0L);

         WinSetDlgItemShort(hwnd, DID_WINDOWLINES, spyopt.cWindowLines, FALSE)
         WinSetDlgItemText(hwnd, DID_FILENAME, spystr.szFileName);


         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:

             /*
              * Now retrieve the output options from the
              * dialog
              */
             spyopt.fWindow = (BOOL)SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
                 DID_WINDOW, BM_QUERYCHECK, 0L, 0L));
             spyopt.fFile = (BOOL)SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
                 DID_FILE, BM_QUERYCHECK, 0L, 0L));

             WinQueryDlgItemShort(hwnd, DID_WINDOWLINES, &spyopt.cWindowLines,
             WinQueryDlgItemText(hwnd, DID_FILENAME,
                 sizeof(spystr.szFileName), spystr.szFileName);

             /*
              * Now take care of file operations
              * If a file is already active, we will continue to use it, and
              * ignore the case where the user may have changed file names
              * Will truncate any file.
              */
             if (spyopt.fFile) {
                  if (spyopt.hfileSpy == NULL)
                     if (DosOpen((PSZ)spystr.szFileName, &spyopt.hfileSpy,
                             (USHORT far *)&wAction, 0L, 0,
                             0x0012, 0x00C1, 0L) != 0)
                         spyopt.hfileSpy = NULL; /* Failed on open */
             } else {
                 if (spyopt.hfileSpy != NULL) {
                     /* file open, not outputing, close it now */
                     DosClose (spyopt.hfileSpy);
                     spyopt.hfileSpy = NULL;
                 }
             }
             /* Fall through to DID_CANCEL */
         case DID_CANCEL:
             /* Now dismiss the dialog */
             WinDismissDlg(hwnd, SHORT1FROMMP(mp1));
             break;
         }
         break;

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;
     }
     return 0L;
 }




 /***************************************************************************\
 * MRESULT EXPENTRY SpySaveListDlgProc(hwnd, msg, mp1, mp2)
 *
 * The Spy Windows Dialog procedure
 \***************************************************************************/
 MRESULT EXPENTRY SpySaveListDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     HFILE   hfileOut;
     char    szTemp[100];
     char    szTime[10];
     char    szDate[10];
     SHORT   cItems;
     SHORT   iItem;
     USHORT  cch;
     USHORT  cchWritten;
     USHORT  wAction;
     ULONG   lTemp;

     switch (msg) {

     case WM_INITDLG:
         WinSendDlgItemMsg(hwnd, DID_APPEND, BM_SETCHECK,
             (MPARAM)spyopt.fAppend, 0L);
         /* Initialize the dialog items */
         WinSetDlgItemText(hwnd, DID_FILENAME, spystr.szSaveFileName);
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:
             /*
              * Get the file name, and try to open the file,
              * Then loop through and dump the listbox contents to the
              * file.
              */
             spyopt.fAppend = (BOOL)SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
                 DID_APPEND, BM_QUERYCHECK, 0L, 0L));

             WinQueryDlgItemText(hwnd, DID_FILENAME,
                 sizeof(spystr.szSaveFileName), spystr.szSaveFileName);

             if (DosOpen((PSZ)spystr.szSaveFileName, (HFILE far *)&hfileOut,
                     (USHORT far *)&wAction, 0L, 0,
                     spyopt.fAppend? 0x0011 : 0x0012, 0x00C1, 0L) == 0) {

                 /* If append, get to the end of the file */
                 if (spyopt.fAppend)
                     DosChgFilePtr(hfileOut, 0L, 2, (PULONG)&lTemp);

                 /* Get count of items */
                 cItems = SHORT1FROMMR(WinSendMsg(hwndSpyList, LM_QUERYITEMCOU
                             0L, 0L));

                 /* Write out a title block to the file */
                 _strdate(szDate);
                 _strtime(szTime);
                 DosWrite(hfileOut,
                         (PSZ)"***************************************\r\n",
                                 41, (PUSHORT)&cchWritten);
                 cch = sprintf(szTemp, "* Spy: %-10s %-10s          *\r\n",
                     szDate, szTime);
                 DosWrite(hfileOut, (PSZ)szTemp, cch, (PUSHORT)&cchWritten);

                 DosWrite(hfileOut,
                         (PSZ)"***************************************\r\n",
                                 41, (PUSHORT)&cchWritten);


                         /* Now output the list to the file */
                 for (iItem = 0; iItem < cItems; iItem++) {
                     cch = SHORT1FROMMR(WinSendMsg(hwndSpyList, LM_QUERYITEMTE
                             MPFROM2SHORT(iItem, sizeof(szTemp)),
                             (MPARAM)(PSZ)szTemp));
                     /* Add Newline at end of string */
                     szTemp[cch++] = '\r';
                     szTemp[cch++] = '\n';
                     szTemp[cch] = '\0';
                     DosWrite(hfileOut, (PSZ)szTemp, cch,
                             (PUSHORT)&cchWritten);
                 }
                 DosClose(hfileOut);
             }

         case DID_CANCEL:
             /* Now dismiss the dialog */
             WinDismissDlg(hwnd, SHORT1FROMMP(mp1));
             break;
         break;
     }

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
     }
     return 0L;
 }




 /**************************************************************************\
 * AboutWndProc(HWND hwnd, USHORT message, MPARAM mp1, MPARAM mp2)
 *
 * About Spy... Dialog procedure
 \***************************************************************************/
 MRESULT EXPENTRY AboutWndProc(hwnd, message, mp1, mp2)
 HWND    hwnd;
 USHORT  message;
 MPARAM   mp1;
 MPARAM   mp2;
 {
     switch (message) {
         case WM_COMMAND:
             WinDismissDlg(hwnd, TRUE);
             break;
         default:
             return(WinDefDlgProc(hwnd, message, mp1, mp2));
             break;
     }
     return 0L;

 } /* end aboutwndproc */



 PAINT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLOCK\PAINT.C

 /*
     paint.c -- Paints the clock

     Created by Microsoft Corporation, 1989
 */
 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "clock.h"
 #include "dialogs.h"

 #define MATLF_SIZE ( sizeof ( MATRIXLF ) / sizeof ( LONG ) )

 #define CLK_MAJORTICKS        0x0001
 #define CLK_MINORTICKS        0x0002

 extern BOOL fIconic , fShowSecondHand ;
 extern DATETIME dt ;
 extern HPS hps ;

 VOID ClkPaint ( HWND ) ;
 VOID ClkDrawHand ( HPS , SHORT , SHORT ) ;
 VOID ClkDrawFace ( HPS ) ;
 VOID ClkDrawTicks ( HPS , USHORT ) ;

 static FIXED fxSin [60] = {
     0x00000000, 0x00001ac2, 0x00003539, 0x00004f1b, 0x0000681f, 0x00007fff,
     0x00009679, 0x0000ab4c, 0x0000be3e, 0x0000cf1b, 0x0000ddb3, 0x0000e9de,
     0x0000f378, 0x0000fa67, 0x0000fe98, 0x0000ffff, 0x0000fe98, 0x0000fa67,
     0x0000f378, 0x0000e9de, 0x0000ddb3, 0x0000cf1b, 0x0000be3e, 0x0000ab4c,
     0x00009679, 0x00008000, 0x00006820, 0x00004f1b, 0x00003539, 0x00001ac2,
     0x00000000, 0xffffe53e, 0xffffcac7, 0xffffb0e5, 0xffff97e1, 0xffff8001,
     0xffff6988, 0xffff54b5, 0xffff41c2, 0xffff30e5, 0xffff224d, 0xffff1622,
     0xffff0c88, 0xffff0599, 0xffff0168, 0xffff0001, 0xffff0167, 0xffff0599,
     0xffff0c88, 0xffff1622, 0xffff224d, 0xffff30e5, 0xffff41c2, 0xffff54b4,
     0xffff6987, 0xffff8000, 0xffff97e0, 0xffffb0e4, 0xffffcac6, 0xffffe53e
 } ;

 USHORT usMajorTickPref = CLKTM_ALWAYS ;
 USHORT usMinorTickPref = CLKTM_NOTICONIC ;
 LONG clrBackground = CLR_DARKCYAN ;
 LONG clrFace = CLR_CYAN ;
 LONG clrHourHand = CLR_PINK ;
 LONG clrMinuteHand = CLR_DARKPINK ;


 /*
     ClkPaint()  -- Paints the clock client window
 */
 VOID ClkPaint ( HWND hwnd )
 {
     RECTL rclUpdate ;

     /* fill the invalid rectangle */
     WinBeginPaint ( hwnd , hps , & rclUpdate ) ;
     WinFillRect ( hps , & rclUpdate , clrBackground ) ;

     /* draw the face, the hour hand, and the minute hand */
     ClkDrawFace ( hps ) ;
     ClkDrawHand ( hps , HT_HOUR , dt . hours ) ;
     ClkDrawHand ( hps , HT_MINUTE , dt . minutes ) ;

     /* draw the tick marks */
     if ( ( usMajorTickPref == CLKTM_ALWAYS ) ||
          ( ( usMajorTickPref == CLKTM_NOTICONIC ) && ! fIconic ) )
         ClkDrawTicks ( hps , CLK_MAJORTICKS ) ;

     if ( ( usMinorTickPref == CLKTM_ALWAYS ) ||
          ( ( usMinorTickPref == CLKTM_NOTICONIC ) && ! fIconic ) )
         ClkDrawTicks ( hps , CLK_MINORTICKS ) ;

     /* draw the second hand last, so xor will work */
     if ( fShowSecondHand )
         ClkDrawHand ( hps , HT_SECOND , dt . seconds ) ;

     /* restore the presentation space */
     WinEndPaint ( hps ) ;
 }


 /*
     ClkDrawHand() -- Draws specified hand at specified hour in given PS
 */
 VOID ClkDrawHand ( HPS hps , SHORT sHandType , SHORT sAngle )
 {
     static POINTL aptlHour [ ] = { { 8 , 0 } , { 0 , 60 } , { -8 , 0 } ,
                                    { 0 , -10 } , { 8 , 0 } } ;
     static POINTL aptlMinute [ ] = { { 6 , 0 } , { 0 , 80 } , { -6 , 0 } ,
                                      { 0 , -15 } , { 6 , 0 } } ;
     static POINTL aptlSecond [ ] = { { 0 , -15 } , { 0 , 85 } } ;
     static LONG cptlHour = sizeof ( aptlHour ) / sizeof ( POINTL ) ;
     static LONG cptlMinute = sizeof ( aptlMinute ) / sizeof ( POINTL ) ;
     static LONG cptlSecond = sizeof ( aptlSecond ) / sizeof ( POINTL ) ;
     BOOL f ;
     static MATRIXLF matlfModel = {
         MAKEFIXED ( 1 , 0 ) ,            MAKEFIXED ( 0 , 0 ) ,        0L ,
         MAKEFIXED ( 0 , 0 ) ,            MAKEFIXED ( 1 , 0 ) ,        0L ,
         100L ,                            100L ,                        1L }

     /* prepare a rotation transform and set it into the ps */
     /*            cos x    - sin x        0     *\
     |            sin x      cos x        0      |
     \*            100        100        1     */

     matlfModel . fxM11 =
     matlfModel . fxM22 = fxSin [ ( sAngle + 15 ) % 60 ] ;
     matlfModel . fxM12 = fxSin [ ( sAngle + 30 ) % 60 ] ;
     matlfModel . fxM21 = fxSin [ sAngle ] ;
     f = GpiSetModelTransformMatrix ( hps , ( LONG ) MATLF_SIZE ,
                                      & matlfModel , TRANSFORM_REPLACE ) ;

     /* draw the specified hand */

     switch ( sHandType ) {

         case HT_HOUR:
             GpiSetColor ( hps , clrHourHand ) ;
             GpiBeginPath ( hps , 1L ) ;
             GpiSetCurrentPosition ( hps , aptlHour ) ;
             GpiPolyLine ( hps , cptlHour , aptlHour ) ;
             GpiEndPath ( hps ) ;
             GpiFillPath ( hps , 1L , FPATH_ALTERNATE ) ;
             break;

         case HT_MINUTE:
             GpiSetColor ( hps , clrMinuteHand ) ;
             GpiBeginPath ( hps , 1L ) ;
             GpiSetCurrentPosition ( hps , aptlMinute ) ;
             GpiPolyLine ( hps , cptlMinute , aptlMinute ) ;
             GpiEndPath ( hps ) ;
             GpiFillPath ( hps , 1L , FPATH_ALTERNATE ) ;
             break;

         case HT_SECOND:
             /* draw in XOR mixmode, so we can undraw later */
             GpiSetMix ( hps , FM_INVERT ) ;
             GpiSetCurrentPosition ( hps , aptlSecond ) ;
             GpiPolyLine ( hps , cptlSecond , aptlSecond ) ;
             GpiSetMix ( hps , FM_OVERPAINT ) ;
             break;
     }
 }


 /*
     ClkDrawFace() -- Draws clock face and tick marks
 */
 VOID ClkDrawFace ( HPS hps )
 {
     BOOL f ;
     LONG l ;
     static POINTL ptlOrigin = { 0L , 0L } ;

     /*              1         0      0     *\
     *              0         1      0      *
     \*            100       100      1     */

     static MATRIXLF matlfModel = {
         MAKEFIXED ( 1 , 0 ) ,            MAKEFIXED ( 0 , 0 ) ,        0L ,
         MAKEFIXED ( 0 , 0 ) ,            MAKEFIXED ( 1 , 0 ) ,        0L ,
         100L ,                            100L ,                        1L }

     /* center at (100, 100) and draw the clock face */
     f = GpiSetModelTransformMatrix ( hps , ( LONG ) MATLF_SIZE ,
                                      & matlfModel , TRANSFORM_REPLACE ) ;
     GpiSetColor ( hps , clrFace ) ;
     GpiSetCurrentPosition ( hps , & ptlOrigin ) ;
     l = GpiFullArc ( hps , DRO_OUTLINEFILL , MAKEFIXED ( 85 , 0 ) ) ;
 }


 /*
     ClkDrawTicks() -- Draws clock ticks
 */
 VOID ClkDrawTicks ( HPS hps , USHORT usTicks )
 {
     BOOL f ;
     USHORT usAngle ;

     /* prepare a transform to use when rotating the ticks */
     /*            cos x    - sin x        0     *\
     |            sin x      cos x        0      |
     \*            100        100        1     */

     static MATRIXLF matlfModel = {
         MAKEFIXED ( 1 , 0 ) ,            MAKEFIXED ( 0 , 0 ) ,        0L ,
         MAKEFIXED ( 0 , 0 ) ,            MAKEFIXED ( 1 , 0 ) ,        0L ,
         100L ,                            100L ,                        1L }

     /* define what the tick marks look like */
     static POINTL aptlMajorTick [ ] = { { -3 , 94 } , { 3 , 100 } } ;
     static POINTL aptlMinorTick [ ] = { { 0 , 94 } , { 0 , 95 } } ;

     /* draw in the default color */
     GpiSetColor ( hps , CLR_DEFAULT ) ;

     /* have we been asked to draw the major ticks? */
     if ( usTicks & CLK_MAJORTICKS )
         for ( usAngle = 0 ; usAngle < 60 ; usAngle += 5 ) {

             /* set the rotation transform */
             matlfModel . fxM11 =
             matlfModel . fxM22 = fxSin [ ( usAngle + 15 ) % 60 ] ;
             matlfModel . fxM12 = fxSin [ ( usAngle + 30 ) % 60 ] ;
             matlfModel . fxM21 = fxSin [ usAngle ] ;
             f = GpiSetModelTransformMatrix ( hps , ( LONG ) MATLF_SIZE ,
                                              & matlfModel ,
                                              TRANSFORM_REPLACE ) ;

             /* draw a major tick mark */
             GpiSetCurrentPosition ( hps , & aptlMajorTick [ 0 ] ) ;
             GpiBox ( hps , DRO_FILL , & aptlMajorTick [ 1 ] , 0L , 0L ) ;
     }

     /* have we been asked to draw the minor ticks? */
     if ( usTicks & CLK_MINORTICKS )
         for ( usAngle = 0 ; usAngle < 60 ; usAngle ++ ) {

             matlfModel . fxM11 =
             matlfModel . fxM22 = fxSin [ ( usAngle + 15 ) % 60 ] ;
             matlfModel . fxM12 = fxSin [ ( usAngle + 30 ) % 60 ] ;
             matlfModel . fxM21 = fxSin [ usAngle ] ;

             /* set the transform */
             f = GpiSetModelTransformMatrix ( hps , ( LONG ) MATLF_SIZE ,
                                              & matlfModel ,
                                              TRANSFORM_REPLACE ) ;

             /* draw a minor tick mark */
             GpiSetCurrentPosition ( hps , & aptlMinorTick [ 0 ] ) ;
             GpiLine ( hps , & aptlMinorTick [ 1 ] ) ;
     }
 }


 PC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PIPES\PC.C

 /*
  * Child program for pipes example. This program
  * is a filter which transliterates lower case
  * characters into upper case and passes others
  * through unchanged.
  *
  * Created by Microsoft Corp. 1987
  */

 #define INCL_DOSPROCESS

 #include <os2def.h>
 #include <bsedos.h>
 #include <stdio.h>

 main()
 {
         int c;

         while((c = getchar()) != EOF)
                 putchar(toupper(c));
         printf("Child saw an EOF\n");
         fflush(stdout);
         DosExit(EXIT_THREAD,0);
 }


 PCHILD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PIPES\PCHILD.C

 /***        pchild.c - child program
  *
  *        Example of DosMakePipe usage in parent/child communication
  *
  *        This is the child program which read the data sent down
  *        from the parent in the pipe.
  *
  * Created by Microsoft Corp. 1987
  */
 #define INCL_DOSFILEMGR
 #define INCL_DOSMEMMGR

 #include <os2def.h>
 #include <stdio.h>
 #include <bsedos.h>

 typedef struct {
         SHANDLE read_handle;               /* pipe read handle */
         SHANDLE write_handle;               /* pipe write handle */
 } SharedData;


 main()
 {

         static char pname[] = "\\SHAREMEM\\public";   /* shared mem seg name
         char *written = "Writing to the child";              /* string in pip
         char readin[21];                /* DosRead input buffer */
         int retcode;                        /* holds return code from call */
         SharedData far *fp;                /* pointer to shared memory */
         SEL mem_handle;                 /* selector of the allocated segment
         USHORT buflen = 21;                /* DosRead buffer length */
         USHORT read;                        /* number bytes read by DosRead *


         /* access shared memory 'public' */
         printf("Accessing shared memory\n");
         retcode = DosGetShrSeg((PSZ)pname, &mem_handle);

         /* create pointer to shared memory segment */
         fp = (SharedData far *)MAKEP(mem_handle,0);

         /* read from the pipe */
         printf("Reading from pipe\n");
         if( retcode = DosRead( fp->read_handle, readin, buflen,
                         &read)) {
                 printf("Read from pipe handle %d failed, retcode %d\n",
                     fp->read_handle, retcode);
         }
         else {
                 printf("DosRead read %d bytes from handle %d, retcode %d\n",
                     read, fp->read_handle, retcode);

                 /* verify the string */
                 if ( retcode = strcmp( written, readin ) ) {
                         printf("The child didn't read pipe data correctly");
                         printf(", retcode %d\n", retcode);
                         printf("read string %s\n", readin);
                         printf("expected string %s\n", written);
                 }
                 else
                         printf("Read pipe data ok\n");
         }

         /* free the segment and return to parent */
         DosFreeSeg( mem_handle );

         printf("Exiting child\n");

         /* Exit without terminating other children */

         DosExit(EXIT_THREAD,0);
 }


 PIPES.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PIPES\PIPES.C

 /***        pipes.c - parent program
  *
  *        Example of DosMakePipe usage in parent/child communication
  *
  *        This program gets some shared memory, makes a pipe, writes
  *        a string into it, and then execs a child.  The child gets
  *        the shared memory segment, then reads from the pipe using
  *        the handle passed in the shared memory segment.
  *
  * Created by Microsoft Corp. 1987
  */

 #define INCL_DOSPROCESS
 #define INCL_DOSFILEMGR
 #define INCL_DOSMEMMGR
 #define INCL_DOSQUEUES

 #include <os2def.h>
 #include <stdio.h>
 #include <bsedos.h>

 typedef struct {                        /* structure of shared mem segment */
         SHANDLE  read_handle;                /* pipe read handle */
         SHANDLE  write_handle;                /* pipe write handle */
 } SharedData;

 SharedData far *fp;                        /* pointer to shared memory */
 SEL mem_handle;                         /* selector of the allocated segment


 main()
 {

         static char pname[] = "\\SHAREMEM\\public";           /* shared mem n
         static char writeout[] = "Writing to the child";   /* pipe string */
         char exec_buf[100];                /* buffer for DosExecPgm ObjName *
         char *pgmname = "pchild.exe";        /* name of child program */
         int i;
         int retcode;                        /* holds return code from call */
         RESULTCODES           tcodes;        /* termination codes from DosExe
         unsigned far *fpinit;                /* pointer to shared memory */
         USHORT buflen;                        /* DosWrite buffer length */
         USHORT memsiz = 1024;                /* size of memory segment reques
         USHORT pipe_size;                /* size to reserve for the pipe */
         USHORT written;                 /* number bytes written by DosWrite *

         /* allocate 1k shared memory segment and initialize it */
         printf("Getting shared memory segment\n");
         retcode = DosAllocShrSeg( memsiz, (PSZ)pname,
                         (PSEL)&mem_handle);

         /* create pointers to shared memory segment */
         fp = (SharedData far *)MAKEP(mem_handle,0);
         fpinit = (unsigned far *)MAKEP(mem_handle,0);

         /* zero initialize the segment */
         for (i = 0; i < memsiz / 2; i++) {
             *fpinit = 0;
             fpinit++;
         }

         /* make the pipe */
         pipe_size = 0;                        /* use default size */
         printf("Making the pipe\n");
         retcode = DosMakePipe((PHFILE)&fp->read_handle,
                         (PHFILE)&fp->write_handle, pipe_size);

         if ( retcode ) {
             printf("DosMakePipe returned error %d, aborting\n", retcode);
             exit(2);
         }
         else
             printf("DosMakePipe retcode %d, read_handle %d, write_handle %d\n
                 retcode, fp->read_handle, fp->write_handle);

         /* write string to pipe */
         buflen = strlen(writeout) + 1;
         if( retcode = DosWrite( fp->write_handle, (PCHAR)writeout,
                         buflen, &written)) {
                 printf("Write to pipe failed, retcode %d\n", retcode);
                 exit(2);
         }
         else
                 printf("Write to write_handle %d, bytes written %d\n",
                     fp->write_handle, written);

         /* create the child */
         printf("Creating child\n");
         retcode = DosExecPgm( exec_buf, 100, EXEC_SYNC,
                         (PSZ)0L, (PSZ)0L,
                         &tcodes, pgmname );

         if ( retcode )
                 printf("DosExecPgm of child error %d\n", retcode);
         else
                 printf("Back in parent\n");

         /* close the pipe */
         DosClose( fp->read_handle );
         DosClose( fp->write_handle );

         DosExit(EXIT_PROCESS,0);   /* Terminate, kill any dangling children *

 }


 PMCAP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PMCAP\PMCAP.C

 /*--------------------------------------------
    PMCAP.C -- Captures PM Screen to .BMP File
   --------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include "pmcap.h"

 #define ID_TIMER    1

 typedef struct
      {
      BOOL  fMonochrome ;
      BOOL  fHideWindow ;
      BOOL  fAutoDialog ;
      BOOL  fIncludePtr ;
      SHORT sDelay ;
      SHORT sDisplay ;
      }
      SETTINGS ;

 MRESULT CALLBACK ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT CALLBACK AboutDlgProc  (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT CALLBACK SaveDlgProc   (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass [] = "PMCAP" ;
 HAB  hab ;

 int main (void)
      {
      static SWCNTRL swctl ;
      static ULONG   flFrameFlags = FCF_TITLEBAR   | FCF_SYSMENU |
                                    FCF_SIZEBORDER | FCF_MINMAX  |
                                    FCF_MENU       | FCF_ACCELTABLE |
                                    FCF_SHELLPOSITION;
      HMQ            hmq ;
      HSWITCH        hsw ;
      HWND           hwndFrame, hwndClient ;
      QMSG           qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      "PMCAP - Captures PM Screen",
                                      0L, (HMODULE) NULL, ID_RESOURCE, &hwndCl

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      swctl.hwnd = hwndFrame ;
      hsw = WinAddSwitchEntry (&swctl) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinRemoveSwitchEntry (hsw) ;
      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT CALLBACK ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR     szFilename [81],
                      szFilePaint [81],
                      szAppName [] = "PMCAP",
                      szKeySet  [] = "SETTINGS",
                      szKeyFile [] = "FILENAME" ,
                      szKeyPaint[] = "FILEPAINT" ;
      static HBITMAP  hbm ;
      static HWND     hwndFrame, hwndMenu ;
      static SETTINGS set = { FALSE, FALSE, TRUE, FALSE,
                              IDM_DELAY15, IDM_ACTUAL } ;
      static SHORT    cxScreen, cyScreen, sCountDown ;
      HBITMAP         hbmClip ;
      HPS             hps ;
      RECTL           rcl ;
      SHORT           sSaveResult, sLen ;
      USHORT          usfInfo ;

      switch (msg)
           {
           case WM_CREATE:
                cxScreen = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN
                cyScreen = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CYSCREEN

                WinQueryProfileData   (hab, szAppName, szKeySet, &set, &sLen);
                WinQueryProfileString (hab, szAppName, szKeyFile,
                                       "BITMAP00.BMP", szFilename,
                                       sizeof szFilename - 1) ;
                WinQueryProfileString (hab, szAppName, szKeyPaint,
                                       "PAINT00.MSP", szFilePaint,
                                       sizeof szFilePaint - 1) ;

                SaveColorSettings (TRUE, set.fMonochrome) ;

                hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
                hwndMenu  = WinWindowFromID (hwndFrame, FID_MENU) ;

                CheckMenuItem (hwndMenu, IDM_MONO,     set.fMonochrome) ;
                CheckMenuItem (hwndMenu, IDM_HIDE,     set.fHideWindow) ;
                CheckMenuItem (hwndMenu, IDM_AUTODLG,  set.fAutoDialog) ;
                CheckMenuItem (hwndMenu, IDM_POINTER,  set.fIncludePtr) ;
                CheckMenuItem (hwndMenu, set.sDelay,   TRUE) ;
                CheckMenuItem (hwndMenu, set.sDisplay, TRUE) ;

                AddItemToSysMenu (hwndFrame) ;
                return 0 ;

           case WM_INITMENU:
                switch (SHORT1FROMMP (mp1))
                     {
                     case IDM_FILE:
                          EnableMenuItem (hwndMenu, IDM_SAVE, hbm != NULL) ;
                          EnableMenuItem (hwndMenu, IDM_SAVEPAINT,
                                          IsBitmapMonoEGA (hbm)) ;
                          return 0 ;

                     case IDM_EDIT:
                          EnableMenuItem (hwndMenu, IDM_COPY, hbm != NULL) ;
                          EnableMenuItem (hwndMenu, IDM_PASTE,
                               WinQueryClipbrdFmtInfo (hab, CF_BITMAP, &usfInf
                                    && usfInfo == CFI_HANDLE) ;
                          return 0 ;
                     }
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_ABOUT:
                          WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc,
                                     (HMODULE) NULL, IDD_ABOUT, NULL) ;
                          return 0 ;

                     case IDM_SAVE:
                          if (!WinDlgBox (HWND_DESKTOP, hwnd, SaveDlgProc,
                                          (HMODULE) NULL, IDD_SAVE, szFilename
                               return 0 ;

                          if (hbm != NULL)
                               {
                               WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_WAIT, FALSE)) ;

                               sSaveResult = SaveBitmap (hbm, szFilename) ;

                               WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_ARROW, FALSE)) ;

                               if (sSaveResult)
                                    ErrorMessage (hwnd, sSaveResult) ;
                               else
                                    IncrementFilename (szFilename) ;
                               }
                          return 0 ;

                     case IDM_SAVEPAINT:
                          if (!WinDlgBox (HWND_DESKTOP, hwnd, SaveDlgProc,
                                          (HMODULE) NULL, IDD_SAVEPAINT, szFil
                               return 0 ;

                          if (IsBitmapMonoEGA (hbm))
                               {
                               WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_WAIT, FALSE)) ;

                               sSaveResult = SavePaintFormat (hbm, szFilePaint

                               WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_ARROW, FALSE)) ;

                               if (sSaveResult)
                                    ErrorMessage (hwnd, sSaveResult) ;
                               else
                                    IncrementFilename (szFilePaint) ;
                               }
                          return 0 ;

                     case IDM_COPY:
                                              // Make copy of stored bitmap

                          hbmClip = CopyBitmap (hbm) ;

                                              // Set clipboard data to copy

                          if (hbmClip != NULL)
                               {
                               WinOpenClipbrd (hab) ;
                               WinEmptyClipbrd (hab) ;
                               WinSetClipbrdData (hab, (ULONG) hbmClip,
                                                  CF_BITMAP, CFI_HANDLE) ;
                               WinCloseClipbrd (hab) ;
                               }
                          else
                               ErrorMessage (hwnd, IDS_BMPCREATE) ;
                          return 0 ;

                     case IDM_PASTE:
                                               // Get bitmap from clipboard

                          WinOpenClipbrd (hab) ;
                          hbmClip = (HBITMAP) WinQueryClipbrdData (hab, CF_BIT

                          if (hbmClip != NULL)
                               {
                               if (hbm != NULL)
                                    GpiDeleteBitmap (hbm) ;

                                              // Make copy of it

                               hbm = CopyBitmap (hbmClip) ;

                               if (hbm == NULL)
                                    ErrorMessage (hwnd, IDS_BMPCREATE) ;
                               }
                          WinCloseClipbrd (hab) ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;

                     case IDM_BEGIN:
                          if (WinStartTimer (hab, hwnd, ID_TIMER, 1000))
                               {
                               EnableMenuItem (hwndMenu, IDM_BEGIN, FALSE) ;
                               EnableMenuItem (hwndMenu, IDM_ABORT, TRUE) ;
                               sCountDown = set.sDelay - IDM_DELAY ;

                               if (set.fHideWindow)
                                    WinShowWindow (hwndFrame, FALSE) ;
                               }
                          else
                               ErrorMessage (hwnd, IDS_TIMER) ;

                          return 0 ;

                     case IDM_ABORT:
                          EnableMenuItem (hwndMenu, IDM_BEGIN, TRUE) ;
                          EnableMenuItem (hwndMenu, IDM_ABORT, FALSE) ;

                          WinStopTimer (hab, hwnd, ID_TIMER) ;
                          return 0 ;

                     case IDM_MONO:
                          CheckMenuItem (hwndMenu, IDM_MONO,
                                         set.fMonochrome = !set.fMonochrome) ;

                          SaveColorSettings (FALSE, set.fMonochrome) ;
                          return 0 ;

                     case IDM_HIDE:
                          CheckMenuItem (hwndMenu, IDM_HIDE,
                                         set.fHideWindow = !set.fHideWindow) ;
                          return 0 ;

                     case IDM_AUTODLG:
                          CheckMenuItem (hwndMenu, IDM_AUTODLG,
                                         set.fAutoDialog = !set.fAutoDialog) ;
                          return 0 ;

                     case IDM_POINTER:
                          CheckMenuItem (hwndMenu, IDM_POINTER,
                                         set.fIncludePtr = !set.fIncludePtr) ;
                          return 0 ;

                     case IDM_DELAY5:
                     case IDM_DELAY10:
                     case IDM_DELAY15:
                     case IDM_DELAY30:
                     case IDM_DELAY60:
                          CheckMenuItem (hwndMenu, set.sDelay, FALSE) ;
                          set.sDelay = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwndMenu, set.sDelay, TRUE) ;
                          return 0 ;

                     case IDM_ACTUAL:
                     case IDM_STRETCH:
                          CheckMenuItem (hwndMenu, set.sDisplay, FALSE) ;
                          set.sDisplay = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwndMenu, set.sDisplay, TRUE) ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;
                     }

           case WM_TIMER:
                switch (SHORT1FROMMP (mp1))
                     {
                     case ID_TIMER:
                          if (--sCountDown)
                               {
                               DosBeep (1024, 100) ;
                               return 0 ;
                               }

                          DosBeep (1024, 1000) ;
                          WinStopTimer (hab, hwnd, ID_TIMER) ;
                          EnableMenuItem (hwndMenu, IDM_BEGIN, TRUE) ;
                          EnableMenuItem (hwndMenu, IDM_ABORT, FALSE) ;

                                              // Delete old bitmap
                          if (hbm != NULL)
                               GpiDeleteBitmap (hbm) ;

                                              // Copy screen to bitmap

                          hbm = ScreenToBitmap (cxScreen, cyScreen,
                                                set.fIncludePtr,
                                                set.fMonochrome) ;

                          if (set.fHideWindow)
                               WinShowWindow (hwndFrame, TRUE) ;

                          if (hbm == NULL)
                               ErrorMessage (hwnd, IDS_BMPCREATE) ;

                          else if (set.fAutoDialog)
                               WinPostMsg (hwnd, WM_COMMAND,
                                    MPFROMSHORT (
                                         set.fMonochrome ? IDM_SAVEPAINT
                                                         : IDM_SAVE),
                                         NULL) ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          WinUpdateWindow (hwnd) ;
                          return 0 ;
                     }
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (hbm != NULL)
                     {
                     WinQueryWindowRect (hwnd, &rcl) ;

                     WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
                                    CLR_NEUTRAL, CLR_BACKGROUND,
                                    set.sDisplay == IDM_STRETCH ?
                                         DBM_STRETCH : DBM_NORMAL) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (hbm != NULL)
                     GpiDeleteBitmap (hbm) ;

                WinWriteProfileString (hab, szAppName, szKeyFile, szFilename)
                WinWriteProfileString (hab, szAppName, szKeyPaint,szFilePaint)
                WinWriteProfileData   (hab, szAppName, szKeySet,
                                       &set, sizeof set) ;

                if (set.fMonochrome)
                     SaveColorSettings (FALSE, FALSE) ;

                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT CALLBACK AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                     case DID_CANCEL:
                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT CALLBACK SaveDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      static PCHAR szFilename ;
      FILE         *file ;

      switch (msg)
           {
           case WM_INITDLG:
                szFilename = PVOIDFROMMP (mp2) ;

                WinSendDlgItemMsg (hwnd, IDD_FILENAME, EM_SETTEXTLIMIT,
                                   MPFROMSHORT (80), NULL) ;

                WinSetDlgItemText (hwnd, IDD_FILENAME, szFilename) ;
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                          WinQueryDlgItemText (hwnd, IDD_FILENAME,
                                               80, szFilename) ;

                                    // Test if file exists

                          if (file = fopen (szFilename, "r"))
                               {
                               fclose (file) ;

                               if (MBID_NO == WinMessageBox (HWND_DESKTOP, hwn
                                                   "File exists.  Replace it?"
                                                   szClientClass, 0,
                                                   MB_YESNO | MB_ICONQUESTION
                                                   MB_DEFBUTTON2 | MB_MOVEABLE
                                    return 0 ;
                               }

                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;

                     case DID_CANCEL:
                          WinDismissDlg (hwnd, FALSE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }


 PMCAP2.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PMCAP\PMCAP2.C

 /*----------------------------------
    PMCAP2.C -- Routines for PMCAP.C
   ----------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_BITMAPFILEFORMAT
 #include <os2.h>
 #include <ctype.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include "pmcap.h"

 extern CHAR szClientClass [] ;
 extern HAB  hab ;

 VOID CheckMenuItem (HWND hwndMenu, SHORT idItem, BOOL fCheck)
      {
      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (idItem, TRUE),
                  MPFROM2SHORT (MIA_CHECKED, fCheck ? MIA_CHECKED : 0)) ;
      }

 VOID EnableMenuItem (HWND hwndMenu, SHORT idItem, BOOL fEnable)
      {
      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (idItem, TRUE),
                  MPFROM2SHORT (MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)) ;
      }

 VOID ErrorMessage (HWND hwnd, SHORT idError)
      {
      CHAR achString [80] ;

      WinLoadString (hab, (HMODULE) NULL, idError, sizeof achString, achString

      WinMessageBox (HWND_DESKTOP, hwnd, achString, szClientClass, 0,
                     MB_OK | MB_ICONEXCLAMATION | MB_MOVEABLE) ;
      }

 VOID AddItemToSysMenu (HWND hwndFrame)
      {
      static CHAR     *szMenuText [2] = { NULL, "~Begin countdown" } ;
      static MENUITEM mi [2] = {
                               MIT_END, MIS_SEPARATOR, 0,         0, 0, 0,
                               MIT_END, MIS_TEXT,      0, IDM_BEGIN, 0, 0
                               } ;
      HWND            hwndSysMenu, hwndSysSubMenu ;
      MENUITEM        miSysMenu ;
      SHORT           idSysMenu, sItem ;

      hwndSysMenu = WinWindowFromID (hwndFrame, FID_SYSMENU) ;
      idSysMenu   = SHORT1FROMMR (WinSendMsg (hwndSysMenu,
                                              MM_ITEMIDFROMPOSITION,
                                              NULL, NULL)) ;

      WinSendMsg (hwndSysMenu, MM_QUERYITEM,
                  MPFROM2SHORT (idSysMenu, FALSE),
                  MPFROMP (&miSysMenu)) ;

      hwndSysSubMenu = miSysMenu.hwndSubMenu ;

      for (sItem = 0 ; sItem < 2 ; sItem++)
           WinSendMsg (hwndSysSubMenu, MM_INSERTITEM,
                       MPFROMP (mi + sItem),
                       MPFROMP (szMenuText [sItem])) ;
      return ;
      }

 VOID SaveColorSettings (BOOL fSave, BOOL fSetMono)
      {
      static LONG clrTableDef [2 * SYSCLR_CSYSCOLORS] ;
      static LONG clrTable [] = { SYSCLR_WINDOWSTATICTEXT, 0x000000,
                                  SYSCLR_SCROLLBAR,        0xC0C0C0,
                                  SYSCLR_BACKGROUND,       0xFFFFFF,
                                  SYSCLR_ACTIVETITLE,      0x000000,
                                  SYSCLR_INACTIVETITLE,    0xFFFFFF,
                                  SYSCLR_MENU,             0xFFFFFF,
                                  SYSCLR_WINDOW,           0xFFFFFF,
                                  SYSCLR_WINDOWFRAME,      0x000000,
                                  SYSCLR_MENUTEXT,         0x000000,
                                  SYSCLR_WINDOWTEXT,       0x000000,
                                  SYSCLR_TITLETEXT,        0xFFFFFF,
                                  SYSCLR_ACTIVEBORDER,     0xA0A0A0,
                                  SYSCLR_INACTIVEBORDER,   0xFFFFFF,
                                  SYSCLR_APPWORKSPACE,     0xE0E0E0,
                                  SYSCLR_HELPBACKGROUND,   0xFFFFFF,
                                  SYSCLR_HELPTEXT,         0x000000,
                                  SYSCLR_HELPHILITE,       0x000000 } ;
      SHORT i ;

      if (fSave)
           for (i = 0 ; i < SYSCLR_CSYSCOLORS ; i++)
                clrTableDef[2*i+1] = WinQuerySysColor (HWND_DESKTOP,
                                         clrTableDef[2*i] = clrTable[2*i], 0L)

      if (fSetMono)
           WinSetSysColors (HWND_DESKTOP, 0L, LCOLF_INDRGB,
                            0L, 2 * SYSCLR_CSYSCOLORS, clrTable) ;
      else
           WinSetSysColors (HWND_DESKTOP, 0L, LCOLF_INDRGB,
                            0L, 2 * SYSCLR_CSYSCOLORS, clrTableDef) ;
      return ;
      }

 VOID IncrementFilename (CHAR *pchName)
      {
      SHORT sIndex ;

      for (sIndex = strlen (pchName) - 1 ; sIndex >= 0 ; sIndex--)
           {
           if (pchName [sIndex] == '\\')      // past filename
                return ;

           if (isdigit (pchName [sIndex]))
                break ;
           }

      for ( ; sIndex >= 0 ; sIndex--)
           {
           if (!isdigit (pchName [sIndex]))
                return ;

           if (pchName [sIndex] == '9')
                pchName [sIndex] = '0' ;
           else
                {
                pchName [sIndex] += '\001' ;
                return ;
                }
           }
      return ;
      }

 HBITMAP CopyBitmap (HBITMAP hbmSrc)
      {
      BITMAPINFOHEADER bmp ;
      HBITMAP          hbmDst ;
      HDC              hdcSrc, hdcDst ;
      HPS              hpsSrc, hpsDst ;
      POINTL           aptl[3] ;
      SIZEL            sizl ;

      hdcSrc = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;
      hdcDst = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsSrc = GpiCreatePS (hab, hdcSrc, &sizl, PU_PELS    | GPIF_DEFAULT |
                                                GPIT_MICRO | GPIA_ASSOC) ;

      hpsDst = GpiCreatePS (hab, hdcDst, &sizl, PU_PELS    | GPIF_DEFAULT |
                                                GPIT_MICRO | GPIA_ASSOC) ;

      GpiQueryBitmapParameters (hbmSrc, &bmp) ;
      hbmDst = GpiCreateBitmap (hpsDst, &bmp, 0L, NULL, NULL) ;

      if (hbmDst != NULL)
           {
           GpiSetBitmap (hpsSrc, hbmSrc) ;
           GpiSetBitmap (hpsDst, hbmDst) ;

           aptl[0].x = aptl[0].y = 0 ;
           aptl[1].x = bmp.cx ;
           aptl[1].y = bmp.cy ;
           aptl[2]   = aptl[0] ;

           GpiBitBlt (hpsDst, hpsSrc, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE) ;
           }
      GpiDestroyPS (hpsSrc) ;
      GpiDestroyPS (hpsDst) ;
      DevCloseDC (hdcSrc) ;
      DevCloseDC (hdcDst) ;

      return hbmDst ;
      }

 VOID CopyPointerToScreen (HPS hpsScreen, LONG alBitmapFormats[], BOOL fCopy)
      {
      static HBITMAP     hbm ;
      static HDC         hdcMemory ;
      static HPS         hpsMemory ;
      static POINTERINFO ptri ;
      static POINTL      ptlPointer, aptl[3] ;
      static SHORT       cxPointer, cyPointer ;
      BITMAPINFOHEADER   bmp ;
      HPOINTER           hptr ;
      SIZEL              sizl ;

      if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT) == 0L)
           return ;

      if (fCopy)
           {
                                    // Get Pointer Information

           cxPointer = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CXPOINTER) ;
           cyPointer = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CYPOINTER) ;

           hptr = WinQueryPointer (HWND_DESKTOP) ;
           WinQueryPointerInfo (hptr, &ptri) ;
           WinQueryPointerPos (HWND_DESKTOP, &ptlPointer) ;

                                    // Create memory DC and PS

           hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

           sizl.cx = sizl.cy = 0 ;
           hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                                    PU_PELS    | GPIF_DEFAULT |
                                    GPIT_MICRO | GPIA_ASSOC) ;

                                    // Create bitmap for destination

           bmp.cbFix     = sizeof bmp ;
           bmp.cx        = cxPointer ;
           bmp.cy        = cyPointer ;
           bmp.cPlanes   = (USHORT) alBitmapFormats[0] ;
           bmp.cBitCount = (USHORT) alBitmapFormats[1] ;

           hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, NULL, NULL) ;

                                    // Copy from screen to bitmap

           GpiSetBitmap (hpsMemory, hbm) ;

           aptl[0].x = 0 ;
           aptl[0].y = 0 ;
           aptl[1].x = cxPointer ;
           aptl[1].y = cyPointer ;
           aptl[2].x = ptlPointer.x - ptri.xHotspot ;
           aptl[2].y = ptlPointer.y - ptri.yHotspot ;

           GpiBitBlt (hpsMemory, hpsScreen, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE)

                                    // Draw pointer on screen

           WinDrawPointer (hpsScreen, (SHORT) aptl[2].x, (SHORT) aptl[2].y,
                           hptr, DP_NORMAL) ;
           }
      else
           {
                                    // Copy from bitmap to screen

           aptl[0].x = ptlPointer.x - ptri.xHotspot ;
           aptl[0].y = ptlPointer.y - ptri.yHotspot ;
           aptl[1].x = aptl[0].x + cxPointer ;
           aptl[1].y = aptl[0].y + cyPointer ;
           aptl[2].x = 0 ;
           aptl[2].y = 0 ;

           GpiBitBlt (hpsScreen, hpsMemory, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE)

                                    // Clean up

           GpiSetBitmap (hpsMemory, NULL) ;
           GpiDestroyPS (hpsMemory) ;
           DevCloseDC (hdcMemory) ;
           GpiDeleteBitmap (hbm) ;
           }
      }

 HBITMAP ScreenToBitmap (SHORT cxScreen, SHORT cyScreen, BOOL fIncludePtr,
                                                         BOOL fMonochrome)
      {
      BITMAPINFOHEADER bmp ;
      HBITMAP          hbm ;
      HDC              hdcMemory ;
      HPS              hpsScreen, hpsMemory ;
      LONG             alBitmapFormats [2] ;
      POINTL           aptl[3] ;
      SIZEL            sizl ;

                                    // Create memory DC and PS

      hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                               PU_PELS    | GPIF_DEFAULT |
                               GPIT_MICRO | GPIA_ASSOC) ;

                                    // Create bitmap for destination


      bmp.cbFix     = sizeof bmp ;
      bmp.cx        = cxScreen ;
      bmp.cy        = cyScreen ;

      if (fMonochrome)
           {
           bmp.cPlanes   = 1 ;
           bmp.cBitCount = 1 ;
           }
      else
           {
           GpiQueryDeviceBitmapFormats (hpsMemory, 2L, alBitmapFormats) ;

           bmp.cPlanes   = (USHORT) alBitmapFormats[0] ;
           bmp.cBitCount = (USHORT) alBitmapFormats[1] ;
           }

      hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, NULL, NULL) ;

                                    // Copy from screen to bitmap

      if (hbm != NULL)
           {
           GpiSetBitmap (hpsMemory, hbm) ;
           hpsScreen = WinGetScreenPS (HWND_DESKTOP) ;

           aptl[0].x = 0 ;
           aptl[0].y = 0 ;
           aptl[1].x = cxScreen ;
           aptl[1].y = cyScreen ;
           aptl[2].x = 0 ;
           aptl[2].y = 0 ;

           if (fIncludePtr)
                CopyPointerToScreen (hpsScreen, alBitmapFormats, TRUE) ;

           WinLockVisRegions (HWND_DESKTOP, TRUE) ;

           GpiBitBlt (hpsMemory, hpsScreen, 3L, aptl,
                      fMonochrome ? ROP_NOTSRCCOPY : ROP_SRCCOPY, BBO_IGNORE)

           WinLockVisRegions (HWND_DESKTOP, FALSE) ;

           if (fIncludePtr)
                CopyPointerToScreen (hpsScreen, alBitmapFormats, FALSE) ;

           WinReleasePS (hpsScreen) ;
           GpiDestroyPS (hpsMemory) ;
           DevCloseDC (hdcMemory) ;
           }

      return hbm ;
      }

 BOOL IsBitmapMonoEGA (HBITMAP hbm)
      {
      BITMAPINFOHEADER bmp ;

      if (hbm != NULL)
           {
           GpiQueryBitmapParameters (hbm, &bmp) ;

           if (bmp.cx        == 640 &&
               bmp.cy        == 350 &&
               bmp.cPlanes   == 1   &&
               bmp.cBitCount == 1)

                return TRUE ;
           }
      return FALSE ;
      }

 SHORT SaveBitmap (HBITMAP hbm, CHAR *szFilename)
      {
      BITMAPFILEHEADER bfh ;
      BITMAPINFO       *pbmi ;
      BYTE             *pbScan ;
      FILE             *file ;
      HDC              hdcMemory ;
      HPS              hpsMemory ;
      LONG             lBmpDataSize ;
      SHORT            sRgbTableSize, sScanLineSize, sScan ;
      SIZEL            sizl ;

                               // Get bitmap information

      bfh.bmp.cbFix = sizeof (BITMAPINFOHEADER) ;
      GpiQueryBitmapParameters (hbm, &bfh.bmp) ;

      if (bfh.bmp.cPlanes != 1)
           return IDS_MULTIPLANE ;

      sRgbTableSize = (1 << bfh.bmp.cPlanes * bfh.bmp.cBitCount) * sizeof (RGB
      sScanLineSize = ((bfh.bmp.cBitCount * bfh.bmp.cx + 31) / 32) * 4 *
                        bfh.bmp.cPlanes ;
      lBmpDataSize  = (LONG) sScanLineSize * bfh.bmp.cy ;

                               // Open file

      if (NULL == (file = fopen (szFilename, "wb")))
           return IDS_FILEOPEN ;

                               // Set up file header and write it to file

      bfh.usType   = BFT_BMAP ;
      bfh.cbSize   = sizeof (BITMAPFILEHEADER) + sRgbTableSize + lBmpDataSize
      bfh.xHotspot = 0 ;
      bfh.yHotspot = 0 ;
      bfh.offBits  = sizeof (BITMAPFILEHEADER) + sRgbTableSize ;

      fwrite (&bfh, sizeof bfh, 1, file) ;

                               // Create memory DC and PS, and set bitmap in i

      hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl, PU_PELS | GPIF_DEFAULT |
                                                      GPIT_MICRO | GPIA_ASSOC)

      GpiSetBitmap (hpsMemory, hbm) ;

                               // Allocate memory for BITMAPINFO table & scans

      pbmi = malloc (sizeof (BITMAPINFOHEADER) + sRgbTableSize) ;

      pbmi->cbFix     = sizeof (BITMAPINFOHEADER) ;
      pbmi->cPlanes   = bfh.bmp.cPlanes ;
      pbmi->cBitCount = bfh.bmp.cBitCount ;

      pbScan = malloc (sScanLineSize) ;

                               // Loop through scan lines

      for (sScan = 0 ; sScan < (SHORT) bfh.bmp.cy ; sScan++)
           {
           GpiQueryBitmapBits (hpsMemory, (LONG) sScan, 1L, pbScan, pbmi) ;

           if (sScan == 0)
                fwrite (&pbmi->argbColor[0], sRgbTableSize, 1, file) ;

           if (fwrite (pbScan, sScanLineSize, 1, file) == 0)
                break ;
           }
                               // Cleanup after completion

      fclose (file) ;
      free (pbmi) ;
      free (pbScan) ;
      GpiDestroyPS (hpsMemory) ;
      DevCloseDC (hdcMemory) ;

      if (sScan != (SHORT) bfh.bmp.cy)
           {
           unlink (szFilename) ;
           return IDS_DISKFULL ;
           }

      return 0 ;
      }

 typedef struct
      {
      SHORT key1 ;
      SHORT key2 ;
      SHORT dxFile ;
      SHORT dyFile ;
      SHORT ScrAspectX ;
      SHORT ScrAspectY ;
      SHORT PrnAspectX ;
      SHORT PrnAspectY ;
      SHORT dxPrinter ;
      SHORT dyPrinter ;
      SHORT AspCorX ;
      SHORT AspCorY ;
      SHORT wCheck ;
      SHORT res1 ;
      SHORT res2 ;
      SHORT res3 ;
      SHORT bitmap[350][40] ;
      }
      PAINT ;

 SHORT SavePaintFormat (HBITMAP hbm, CHAR *szFilename)
      {
      BITMAPINFO *pbmi ;
      FILE       *file ;
      HDC        hdcMemory ;
      HPS        hpsMemory ;
      PAINT      *ppaint ;
      SHORT      i, j, sHold, sWrite ;
      SIZEL      sizl ;
                               // Allocate memory and open file

      if (!IsBitmapMonoEGA (hbm))
           return IDS_MONOEGA ;

      if (NULL == (ppaint = malloc (sizeof (PAINT))))
           return IDS_MEMORY ;

      if (NULL == (file = fopen (szFilename, "wb")))
           {
           free (ppaint) ;
           return IDS_FILEOPEN ;
           }
                               // Set up Paint header for EGA

      ppaint->key1       = 0x6144 ;
      ppaint->key2       = 0x4D6E ;
      ppaint->dxFile     = 0x0280 ;
      ppaint->dyFile     = 0x015E ;
      ppaint->ScrAspectX = 0x0026 ;
      ppaint->ScrAspectY = 0x0030 ;
      ppaint->PrnAspectX = 0x0026 ;
      ppaint->PrnAspectY = 0x0030 ;
      ppaint->dxPrinter  = 0x0280 ;
      ppaint->dyPrinter  = 0x015E ;
      ppaint->AspCorX    = 0x0000 ;
      ppaint->AspCorY    = 0x0000 ;
      ppaint->wCheck     = 0x2C2A ;
      ppaint->res1       = 0xB80E ;
      ppaint->res2       = 0x0050 ;
      ppaint->res3       = 0x8D50 ;

                               // Create memory DC and PS, and set bitmap in i

      hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl, PU_PELS | GPIF_DEFAULT |
                                                      GPIT_MICRO | GPIA_ASSOC)

      GpiSetBitmap (hpsMemory, hbm) ;

                               // Allocate memory for bitmap header info

      pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;

      pbmi->cbFix     = sizeof (BITMAPINFOHEADER) ;
      pbmi->cx        = 640 ;
      pbmi->cy        = 350 ;
      pbmi->cPlanes   = 1 ;
      pbmi->cBitCount = 1 ;
                               // Get bitmap bits

      GpiQueryBitmapBits (hpsMemory, 0L, 350L, (PBYTE) ppaint->bitmap, pbmi) ;

      free (pbmi) ;
                               // Convert to paint format and write

      for (i = 0 ; i < 350 / 2 ; i++)
           for (j = 0 ; j < 40 ; j++)
                {
                sHold = ~ppaint->bitmap[i][j] ;
                ppaint->bitmap[i][j] = ~ppaint->bitmap[350-i-1][j] ;
                ppaint->bitmap[350-i-1][j] = sHold ;
                }

      sWrite = fwrite (ppaint, sizeof (PAINT), 1, file) ;

                               // Clean up after completion
      fclose (file) ;
      free (ppaint) ;
      GpiDestroyPS (hpsMemory) ;
      DevCloseDC (hdcMemory) ;

      if (sWrite == 0)
           {
           unlink (szFilename) ;
           return IDS_DISKFULL ;
           }
      return 0 ;
      }


 PP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\PIPES\PP.C

 /*
  * DosMakePipe example (pp.c, needs related program pc.c).
  *
  * Pipes are a byte stream oriented IPC mechanism which uses
  * standard DosRead and DosWrite file system calls. Since file
  * handles are used to access the pipe, each process using the
  * pipe needs to know the handles. There are two general ways
  * this is done:
  *
  *        1. Have very closely cooperating processes which notify
  *           each other of the file handles via some private method
  *           such as shared memory or command line arguments.
  *
  *        2. With unrelated processes, a process which reads and writes
  *           from STDIN (handle 0) and STDOUT (handle 1) can be attached
  *           to a pipe without being aware of it.
  *
  * This example demonstrates case #2 since that is the harder of the
  * two to set up. This process, pp.exe, creates a pipe and exec's a
  * child process pc.exe whose stdin is attached to the pipe. The parent
  * process writes a lower case message repeatedly into the pipe, and
  * the child process translates it into upper case and prints it on
  * the screen.
  *
  * Compile as: cl -AL -G2 -Lp pp.c   (parent process)
  *             cl -AL -G2 -Lp pc.c   (child process)
  *
  * Created by Microsoft Corp. 1987
  */
 #define INCL_DOSQUEUES
 #define INCL_DOSFILEMGR
 #define INCL_DOSPROCESS

 #include <os2def.h>
 #include <bsedos.h>
 #include <stdio.h>

 PSIZE 256                        /* pipe buffer size */

 char msg[] = "hello there!\n";                /* message to write down pipe *
 char exec_buf[100];                        /* buffer for DosExecPgm ObjName *
 char *pgmname = "pc.exe";                /* name of child program */

 main()
 {
         HFILE pread;                        /* pipe read handle */
         HFILE pwrite;                        /* pipe write handle */
         HFILE newstdin, newstdout;        /* stdin as a pipe handle */
         USHORT FileHandlState;                /* Used to modify handle inheri
         int i;
         USHORT bytecount;                /* bytes written result */
         RESULTCODES rc2;               /* double return codes for EXEC */

         DosMakePipe(&pread, &pwrite, PSIZE);        /* create the pipe */

         /*
          * Now close our stdin which is attached to the console,
          * and make it refer to the pipe instead.
          */
         DosClose(0);                        /* close stdin */
         newstdin = 0;
         DosDupHandle(pread, &newstdin); /* make pipe = stdin */
         /*
          * Since the child process will normaly inherit the handles of
          * the parent we need to close the input of the pipe.  If we
          * don't then the child will hang on the output of the pipe. (since
          * it still has an open handle to the input).
          *
          * We don't really close the input, simply make it non-inheritable
          * to the child.  Thus the only input to the pipe is from the parent.
          * When the parent closes the pipe, any outstanding READs from the
          * child will return a length of 0 (EOF).
          */
         i = DosQFHandState(pwrite, &FileHandlState);
         if (i) printf("Query of pwrite failed\n");
         FileHandlState &= 0x7F88;        /* Mask bits offensive to the call *
         FileHandlState |= 0x080;        /* Deny inheritance to child */
         i = DosSetFHandState(pwrite, FileHandlState);
         if (i) printf("Set of pwrite failed, i = %x\n",i);
 #ifdef NOCODE
         i = DosQFHandState(pread, &FileHandlState);
         if (i) printf("Query of pread failed\n");
         FileHandlState &= 0x7F88;        /* Mask bits offensive to the call *
         FileHandlState |= 0x080;        /* Deny inheritance to child */
         i = DosSetFHandState(pread, FileHandlState);
         if (i) printf("Set of pread failed, i = %x\n",i);
 #endif
         /* exec child program, which will inherit new stdin */
         DosExecPgm(exec_buf, sizeof(exec_buf), EXEC_ASYNC, (PSZ)0L,
                         (PSZ)0L, &rc2, pgmname);

         /* write 20 messages down the pipe */
         printf("Writing messages to the pipe\n");
         for(i = 0; i < 20; i++)
                 DosWrite(pwrite, msg, sizeof(msg) - 1, &bytecount);

         DosClose(pwrite);
         DosClose(pread);
         printf("Parent exiting\n");
         DosExit(EXIT_PROCESS,0); /* Terminate, kill any lingering children */
 }


 PRINT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\INIEDIT\PRINT.C

 /******************************* Module Header ******************************
 * Module Name: Print.c
 *
 *
 * Created by Microsoft Corporation, 1989
 *
 *
 * PM OS2.ini Editor
 *
 * Printing Functions
 *
 \***************************************************************************/


 #define LINT_ARGS
 #define INCL_WINSHELLDATA
 #define INCL_GPIBITMAPS
 #define INCL_GPIREGIONS
 #define INCL_GPILCIDS
 #define INCL_GPIPRIMITIVES
 #define INCL_DEV

 #include <string.h>
 #include <stdio.h>

 #include <os2.h>

 #include "noncomm.h"
 #include "IniEdit.h"


 /******************************* Constants *********************************/

 #define LEFT_MARGIN      3
 #define TOP_MARGIN       7
 #define BOTTOM_MARGIN   15
 #define        MAX_PRINT_LEN        1024


 /******************************* Externals *********************************/

 extern USHORT        cAppNames;
 extern PGROUPSTRUCT  pGroups;
 extern HAB           habIniEdit;
 extern USHORT        usLineHeight;
 extern USHORT        usPrintFormat;

 /******************************* Internals ********************************/
 CHAR        szBuf[MAX_STRING_LEN];                        // Output character
 CHAR        szPrinter[MAX_PRINT_LEN];                // Logical Printer Name
 CHAR        szDetails[MAX_PRINT_LEN];                // Logical Printer Detai


 /***************************** Function Header *****************************\
 *
 * PrintThread
 *
 *
 * Prints the ini info in specified format
 *
 \***************************************************************************/

 VOID PrintThread()
 {
     HAB      habPrint;                          // HAB of Print Thread
     HDC      hdcPrint;                          // DC of Printer
     HPS      hpsPrint;                          // PS of Printer
     SIZEL    sizel;                             // Size of PS to Create
     POINTL   ptlOutput;                         // Output location
     BYTE     abOut[50];                         // DevEscape Output
     ULONG    ulOut;                             // DevEscape Output Count
     USHORT   usHeight;                          // Height of Printer Page
     USHORT   cch;                               // Count of Characters in buf
     USHORT   i,j;                               // Loop Counters
     DEVOPENSTRUC  dop;                          // DevOpenDC Info
     CHAR     szMessage[80];

     /* initialization of thread */
     if (!(habPrint = WinInitialize(0))) DosExit(EXIT_THREAD, -1);


     /*** Select Port ***/
     /* get name of default logical printer */
     cch = WinQueryProfileString(habPrint, "PM_SPOOLER", "PRINTER",
                            "",                /* default  */
                            szPrinter,
                            MAX_PRINT_LEN);    /* max chars */

     szPrinter[cch-2] = 0;                     /* remove terminating ';' */

     /* get specifics of default logical printer */
     cch = WinQueryProfileString(habPrint, "PM_SPOOLER_PRINTER", szPrinter,
                            "",                /* default  */
                            szDetails,
                            MAX_PRINT_LEN);    /* max chars */


     /* info in form of "port; driver; logical address" */
     /* grab driver name; one past semicolon */
     dop.pszDriverName = strchr( szDetails, ';' )+1;

     /* Grab logical address */
     dop.pszLogAddress = strchr( dop.pszDriverName, ';' )+1;

     /* Make driver and Logical address Null terminated */
     dop.pszLogAddress = strtok( dop.pszLogAddress, ",;" );
     dop.pszDriverName = strtok( dop.pszDriverName, ",.;" );  /* Driver.Model

     dop.pdriv = NULL;
     dop.pszDataType = NULL;

     /* Create Printer DC */
     hdcPrint = DevOpenDC( habPrint, OD_QUEUED, "*", 3L, (PDEVOPENDATA)&dop,
             (HDC)NULL);

     if( hdcPrint == (HDC)NULL )
         {
         DosExit(EXIT_THREAD, -1);
         }

     strcpy( szBuf, (usPrintFormat == APP_FORM) ? SZAPP : SZALL );
     DevEscape( hdcPrint, DEVESC_STARTDOC, (LONG)strlen( szBuf ), (PBYTE)szBuf
             &ulOut, (PBYTE)NULL );

     /* Determine size of page */
     DevQueryCaps( hdcPrint, CAPS_HEIGHT, 1L, (PLONG)&usHeight );
     ptlOutput.y = usHeight - TOP_MARGIN - usLineHeight;
     ptlOutput.x = LEFT_MARGIN;

     /* Create Printer PS */
     sizel.cx = sizel.cy = 0L;
     hpsPrint = GpiCreatePS( habPrint, hdcPrint, &sizel,
             PU_PELS | GPIF_DEFAULT | GPIT_NORMAL | GPIA_ASSOC );

     if( hpsPrint == (HPS)NULL )
         {
         DosExit(EXIT_THREAD, -1);
         }


     /* print items */
     if( usPrintFormat == APP_FORM )
         {
         for( i=0; i<cAppNames; i++ )
             {
             sprintf(szMessage,"CharString: X = %ld,  Y = %ld",
                                                     ptlOutput.x, ptlOutput.y)
             GpiCharStringAt( hpsPrint, &ptlOutput, (LONG)strlen( pGroups[i].s
                     pGroups[i].szAppName );
             ptlOutput.y -= usLineHeight;

             if( ptlOutput.y <= BOTTOM_MARGIN )
                 {
                 DevEscape( hdcPrint, DEVESC_NEWFRAME, 0L, (PBYTE)NULL , &ulOu
                 ptlOutput.y = usHeight - TOP_MARGIN - usLineHeight;
                 }

             }
         }  /* if */
     else
         {
         /* Print App Names */
         for( i=0; i<cAppNames; i++ )
             {
             GpiCharStringAt( hpsPrint, &ptlOutput, (LONG)strlen( pGroups[i].s
                     pGroups[i].szAppName );
             ptlOutput.y -= usLineHeight;
             if( ptlOutput.y <= BOTTOM_MARGIN )
                 {
                 DevEscape( hdcPrint, DEVESC_NEWFRAME, 0L, (PBYTE)NULL , &ulOu
                 ptlOutput.y = usHeight - TOP_MARGIN - usLineHeight;
                 }

             /* Print Key Value Pairs for current App Name */
             for( j=0; j<pGroups[i].cKeys; j++ )
                 {
                 sprintf( szBuf, "   %s: %s", pGroups[i].pPairs[j].szKey,
                         pGroups[i].pPairs[j].szValue );

                 if( GpiCharStringAt( hpsPrint, &ptlOutput, (LONG)strlen( szBu
                         ;

                 ptlOutput.y -= usLineHeight;
                 if( ptlOutput.y <= BOTTOM_MARGIN )
                     {
                     DevEscape( hdcPrint, DEVESC_NEWFRAME, 0L, (PBYTE)NULL , &
                     ptlOutput.y = usHeight - TOP_MARGIN - usLineHeight;
                     }
                 }  /* for */
             }  /* for */

         }

     /* Cleanup */
     DevEscape( hdcPrint, DEVESC_ENDDOC, 0L, (PBYTE)NULL , &ulOut, abOut );
     GpiAssociate( hpsPrint, (HDC)NULL );
     GpiDestroyPS( hpsPrint );
     DevCloseDC( hdcPrint );
     DosExit(EXIT_THREAD, 0);
 }  /* PrintThread */


 PRINT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\STOCK\PRINT.C

 /***        print.c - printing support
  *
  *        This file presents code to make if VERY EASY for an application
  *        to select a printer, manipulate job properties, and open a DC
  *        for printing.
  *
  *        Author:
  *            Benjamin W. Slivka
  *
  *        History:
  *            12-May-1990 bens        Initial version
  *            16-May-1990 bens        Skeletal job property routines
  *            31-May-1990 bens        Final job property routines
  *
  *        APIs Offered:
  *            PrintQueryPrinterList    - Get printer list
  *            PrintQueryNextPrinter    - Enumerate members of printer list
  *            PrintQueryDefaultPrinter - Get default printer
  *            PrintQueryPrinterInfo    - Get information about a printer
  *
  *            PrintOpenDC              - Open a printer DC
  *
  *            PrintQueryJobProperties  - Get JP from a printer
  *            PrintChangeJobProperties - Interact with user to change JP
  *            PrintMatchPrinter             - Find printer which matches JP
  *
  *        Key points:
  *
  *        1)  [PM_SPOOLER,PRINTER] in OS2SYS.INI specifies the default printe
  *                This entry is of use *only* if [PM_SPOOLER,QUEUE] is empty.
  *
  *        2)  If a [PM_SPOOLER_PRINTER,xxx] entry in OS2SYS.INI entry refers
  *                to *more than one* driver, *and* the printer is *not*
  *                associated with a queue, then we always use the first drive
  *                in the list.  There is no information in the system to help
  *                us choose among the drivers, so we make the easy choice.
  */

 #define INCL_DEV

 #define INCL_WINSHELLDATA

 #include <os2.h>

 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #include "ids.h"
 #include "file.h"
 #include "mem.h"
 #include "print.h"


 //************************************************************************
 //
 //  PRIVATE Types and Definitions
 //
 //************************************************************************


 // SEARCHP - Search structure for PrinterBegin/Next/EndSearch
 //
 typedef struct { /* srchp */
     char *  pchName;                        // Driver name list
     char *  pchNext;                        // Next name in achNames to consu
 } SEARCHP;
 typedef SEARCHP *PSEARCHP; /* psrchp */


 // PRINTER - Structure for printer destination (used for DevOpenDC)
 //
 typedef struct _printer {  /* prt */
     LONG    lType;                        // Printer type (OD_DIRECT/OD_QUEUE
     char *  pszName;                        // Name (either queue or printer
                                         //  NOTE: This pointer always points
                                         //          to either pszLogAddr or
                                         //          pszPrinter.  This pointer
                                         //          should never be freed!
     char *  pszLogAddr;                 // Logical address for DevOpenDC
     char *  pszPrinter;                 // Printer Name
     char *  pszDriver;                        // Driver name
     char *  pszModel;                        // Driver model name
     char *  pszDescription;                // Description
     BOOL    fMultipleDrivers;                // TRUE => pszDriver/pszModel co
                                         //        the first driver in the pri
                                         //        definition.  If this is a q
                                         //        (lType == OD_QUEUED), the
                                         //        driver/model must be gotten
                                         //        from PM_SPOOLER_QUEUE_DD.
     USHORT  cbDriverData;                // Length of driver data
     BYTE *  pbDriverData;                // Driver data
     struct _printer *pprtNext;                // Link to next printer
     struct _printer *pprtPrev;                // Link to previous printer
     struct _printerlist *pprtlist;        // Owning list
 } PRINTER;
 typedef PRINTER *PPRINTER;  /* pprt */


 //  PRINTERLIST - Structure for list of printers
 //
 typedef struct _printerlist { /* prtlist */
     HAB         hab;                        // Anchor block for list
     PPRINTER        pprtHead;                // Head of printer list
     PPRINTER        pprtTail;                // Tail of printer list
     PPRINTER        pprtDefault;                // System default printer
 } PRINTERLIST;
 typedef PRINTERLIST *PPRINTERLIST; /* pprtlist */


 //  JOBPROP - Structure for exporting/importing job properties
 //
 typedef struct {    /* jp */
     USHORT  cb;                         // Total buffer size
     USHORT  cbName;                        // Size of printer name (counting
     USHORT  cbDriver;                        // Size of driver name (counting
     USHORT  cbModel;                        // Size of model name (counting N
     USHORT  cbData;                        // Size of driver data
  //
  // The "fields" below show the order in which the strings/data follow the
  // structure "header":
  //
  // CHAR    achName[];                        // Printer name (asciiz)
  // CHAR    achDriver[];                // Driver name (asciiz)
  // CHAR    achModel[];                 // Model name (asciiz)
  // BYTE    abData[];                        // Driver data (binary)
  //
 } JOBPROP;
 typedef JOBPROP *PJOBPROP;  /* pjp */


 //************************************************************************
 //
 //  PRIVATE Global Variables
 //
 //************************************************************************


 static        char        achPath[CCHMAXPATH];


 //************************************************************************
 //
 //  PRIVATE Function Prototypes
 //
 //************************************************************************


 BOOL          AddPrinter(PPRINTERLIST pprtlist,PPRINTER pprtNew);
 PPRINTER  ClonePrinter(PPRINTER pprtOriginal,char *pszLogAddr);
 PPRINTER  CreatePrinter(LONG  lType,
                         char *pszLogAddr,
                         char *Printer,
                         char *pszDriver,
                         char *pszModel,
                         BOOL  fMultipleDrivers);
 VOID          DestroyPrinter(PPRINTER pprt);
 VOID          GetDriverData(PPRINTER pprt);
 VOID          GetDriverModel(PPRINTER pprt);
 VOID          GetDescription(PPRINTER pprt);
 PPRINTER  GetPrinterData(char *pszPrinter);
 PPRINTER  MatchPrinter(PPRINTERLIST pprtlist,char *pszQueue,char *pszPrinter)
 PSEARCHP  PrinterBeginSearch(PPRINTER *ppprt);
 BOOL          PrinterEndSearch(PSEARCHP psrchp);
 PPRINTER  PrinterNextSearch(PSEARCHP psrchp);
 VOID          QueryDefaultPrinterQueue(char **ppszQueue,char **ppszPrinter);
 PPRINTER  SplitPrinter(PPRINTERLIST pprtlist,PPRINTER pprtOld);
 void          TrimTrailingSemicolon(char *psz);


 //************************************************************************
 //
 //  PUBLIC Functions
 //
 //************************************************************************


 /***        PrintCreatePrinterList - Get list of printers
  *
  *        Use PrintQueryNextPrinter to enumerate printers.
  *        Use PrintQueryPrinterInfo to get information on a printer.
  *
  *        Entry
  *            hab - anchor block handle
  *
  *        Exit-Success
  *            Returns HPRINTERLIST
  *
  *        Exit-Failure
  *            Returns NULL
  */
 HPRINTERLIST PrintCreatePrinterList(HAB hab)
 {
     PSEARCHP            psrchp=NULL;
     static PPRINTER pprt;
     PPRINTER            pprtQueue=NULL;
     PPRINTER            pprtPrinter=NULL;
     PPRINTERLIST    pprtlist;
     static char *   pszPrinter;
     static char *   pszQueue;

     // Build initial printer list

     psrchp = PrinterBeginSearch(&pprt); // Get first printer
     if (psrchp == NULL)                 // No printers installed
         return NULL;                        //  Fail

     pprtlist = MemAlloc(sizeof(PRINTERLIST)); // Create list
     if (pprtlist == NULL) {                // No memory
         PrinterEndSearch(psrchp);        // Done searching
         return NULL;                        // Fail
     }
     pprtlist->pprtHead = NULL;
     pprtlist->pprtTail = NULL;

     // Add all printers

     while (pprt != NULL) {                // Get all printers
         AddPrinter(pprtlist,pprt);        // Add to list
         pprt = PrinterNextSearch(psrchp); // Get another printer
     }
     PrinterEndSearch(psrchp);                // Done searching

     // Now we have a list of printers that may have multiple queues
     // associated with them, and may have multiple drivers.
     //
     // Here, we split entries to get one per queue
     // It may still be the case that the driver.model are incorrect
     // for the Queue.  However, we do a "lazy evaluation" of this,
     // using the fMultipleDrivers flag.  See PrintQueryPrinterInfo
     // and GetDriverModel for more details.

     // Split any multi-queue entries

     for (pprt=pprtlist->pprtHead; pprt!=NULL; ) {
         pprt = SplitPrinter(pprtlist,pprt);         // Split, if necessary
     }

     // Find default printer

     QueryDefaultPrinterQueue(&pszQueue,&pszPrinter);
     pprt = MatchPrinter(pprtlist,pszQueue,pszPrinter);
     if (pprt == NULL)                        // No default printer
         pprt = pprtlist->pprtHead;        //  Choose first printer
     pprtlist->pprtDefault = pprt;        // Remember default printer

     if (pszQueue != NULL)                // Free default printer/queue names
         MemFree(pszQueue);
     if (pszPrinter != NULL)
         MemFree(pszPrinter);

     pprtlist->hab = hab;                // Remember hab
     return pprtlist;                        // Return list to caller
 }


 /***        PrintDestroyPrinterList - Destroy printer list
  *
  *        Entry
  *            hprtlist - printer list
  *
  *        Exit-Success
  *            Returns TRUE, list destroyed
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 BOOL          PrintDestroyPrinterList(HPRINTERLIST hprtlist)
 {
     PPRINTER            pprt;
     PPRINTERLIST    pprtlist = hprtlist;
     PPRINTER            pprtNext;

     if (pprtlist == NULL)
         return FALSE;

     MemAssert(pprtlist);

     pprt = pprtlist->pprtHead;                // Start with first printer
     while (pprt != NULL) {                // Loop through list
         pprtNext = pprt->pprtNext;        // Next printer
         DestroyPrinter(pprt);                // Destroy this one
         pprt = pprtNext;                // Advance
     }
     return TRUE;
 }


 /***        PrintQueryNextPrinter - Get a printer from a list of printers
  *
  *        Use PrintQueryPrinterInfo to get information on a printer.
  *
  *        Entry
  *            hprtlist - printer list
  *            hprt     - starting printer
  *                          NULL => return first printer in list
  *                         !NULL => return printer after hprt
  *
  *        Exit-Success
  *            Returns HPRT
  *
  *        Exit-Failure
  *            Returns NULL
  */
 HPRINTER PrintQueryNextPrinter(HPRINTERLIST hprtlist,HPRINTER hprt)
 {
     PPRINTERLIST    pprtlist = hprtlist;
     PPRINTER            pprt = hprt;

     MemAssert(pprtlist);
     if (pprt == NULL)                        // Get first printer
         return pprtlist->pprtHead;
     else {                                // Get next printer
         MemAssert(pprt);
         if (pprt->pprtlist != pprtlist) // Verify hprt is on specified list
             MemAssert(NULL);
         return pprt->pprtNext;
     }
 }


 /***        PrintQueryDefaultPrinter - Return default printer
  *
  *        Entry
  *            hprtlist - printer list
  *
  *        Exit
  *            Returns HPRINTER of default printer.
  */
 HPRINTER PrintQueryDefaultPrinter(HPRINTERLIST hprtlist)
 {
     PPRINTERLIST    pprtlist = hprtlist;

     MemAssert(pprtlist);
     return pprtlist->pprtDefault;
 }


 /***        PrintQueryPrinterInfo - Get info on a printer
  *
  *        Entry
  *            hprt  - printer handle
  *            index - index of printer info
  *
  *        Exit-Success
  *            Returns requested information
  *                PQPI_NAME          - char * - Nice name
  *                PQPI_LOG_ADDRESS  - char * - Logical address
  *                PQPI_DRIVER          - char * - Driver name
  *                PQPI_MODEL          - char * - Model name
  *                PQPI_TYPE          - ULONG  - OD_QUEUED or OD_DIRECT
  *                PQPI_PRINTER          - char * - Printer name
  *                PQPI_DESCRIPTION  - char * - Description
  *
  *        Exit-Failure
  *            Returns -1L
  */
 ULONG PrintQueryPrinterInfo(HPRINTER hprt,USHORT index)
 {
     PPRINTER        pprt = hprt;

     MemAssert(pprt);
     switch (index) {
         case PQPI_NAME:
             return (ULONG)(VOID FAR *)pprt->pszName;

         case PQPI_LOG_ADDRESS:
             return (ULONG)(VOID FAR *)pprt->pszLogAddr;

         case PQPI_DRIVER:
             GetDriverModel(pprt);        // Make sure we get correct data
             return (ULONG)(VOID FAR *)pprt->pszDriver;

         case PQPI_MODEL:
             GetDriverModel(pprt);        // Make sure we get correct data
             return (ULONG)(VOID FAR *)pprt->pszModel;

         case PQPI_TYPE:
             return pprt->lType;

         case PQPI_PRINTER:
             return (ULONG)(VOID FAR *)pprt->pszPrinter;

         case PQPI_DESCRIPTION:
             GetDescription(pprt);        // Get appropriate description
             return (ULONG)(VOID FAR *)pprt->pszDescription;

         default:
             return -1L;
     }
 }


 /***        PrintOpenDC - Open DC on specified printer
  *
  *        Entry
  *            hab         - Anchor block
  *            hprt        - Printer handle
  *            pszDataType - "PM_Q_STD" or "PM_Q_RAW"
  *
  *        Exit-Success
  *            Returns hdc
  *
  *        Exit-Failure
  *            Returns NULL
  */
 HDC PrintOpenDC(HAB hab,HPRINTER hprt,char *pszDataType)
 {
     DEVOPENSTRUC    dop;
     HDC             hdc;
     PPRINTER            pprt = hprt;

     MemAssert(pprt);
     GetDriverData(pprt);                // Make sure we have driver data

     dop.pszLogAddress = pprt->pszLogAddr; // Logical address
     dop.pszDriverName = pprt->pszDriver; // Driver name (PSCRIPT)
     dop.pdriv              = (VOID *)pprt->pbDriverData; // Driver data
     dop.pszDataType   = pszDataType;        // PM_Q_STD or PM_Q_RAW

  // dop.pszComment    = "stock chart";        // Comment for OD_Q
  // dop.pszQueueProcName   = NULL;        // queue processor; NULL => use def
  // dop.pszQueueProcParams = NULL;        // parms for queue processor
  // dop.pszSpoolerParams   = NULL;        // spooler parms (use NULL!)
  // dop.pszNetworkParams   = NULL;        // network parms (use NULL!)

     hdc = DevOpenDC(
             hab,                        // anchor block
             pprt->lType,                // DC type: OD_DIRECT or OD_QUEUED
             "*",                        // device info(?) "*"
             4L,                         // count of info in DEVOPENSTRUC
             (PDEVOPENDATA)&dop,         // DEVOPENSTRUC
             NULL                        // Compatible DC (use NULL?)
     );
     return hdc;
 }


 /***        PrintQueryJobProperties - Get printer job properties
  *
  *        Entry
  *            hprt    - printer handle
  *            pcbData - pointer to size of pb buffer in bytes
  *                        Pass 0 to determine required buffer size.
  *            pbData  - pointer to buffer to receive job properties
  *
  *        Exit-Success
  *            Returns TRUE, job properties retrieved
  *                pb filled in with job property data
  *                pcb filled in with size of pb data
  *
  *        Exit-Failure
  *            Returns FALSE, pb too small for job properties
  *                pcb filled in with required size
  *
  *        Note
  *            (1) It is valid for a printer to require no job properties.
  *                In this case, *pcb == 0.
  */
 BOOL PrintQueryJobProperties(HPRINTER hprt,USHORT *pcbData,BYTE *pbData)
 {
     USHORT        cb;                        // Total buffer size
     USHORT        cbData;                 // Size of driver data
     USHORT        cbDriver;                // Size of driver name (counting N
     USHORT        cbModel;                // Size of model name (counting NUL
     USHORT        cbName;                 // Size of printer name (counting N
     BYTE *        pb;
     PJOBPROP        pjp;
     PPRINTER        pprt = hprt;

     MemAssert(pprt);
     GetDriverData(pprt);                // Make sure we have driver data

     cbName   = strlen(pprt->pszName)+1; // Count NUL
     cbDriver = strlen(pprt->pszDriver)+1; // Count NUL
     cbModel  = strlen(pprt->pszModel)+1; // Count NUL
     cbData   = pprt->cbDriverData;        // Driver data length

     // Check size of return buffer

     cb = sizeof(JOBPROP)+cbName+cbDriver+cbModel+cbData;
     if (*pcbData < cb) {                // Check buffer size
         *pcbData = cb;                        //  Too small, return required
         return FALSE;                        // Indicate failure
     }

     // Fill in return buffer

     pjp = (PJOBPROP)pbData;
     pjp->cb          = cb;
     pjp->cbName   = cbName;
     pjp->cbDriver = cbDriver;
     pjp->cbModel  = cbModel;
     pjp->cbData   = cbData;

     pb = (BYTE *)pjp+sizeof(JOBPROP);        // Start of data space

     pb = memcpy(pb,pprt->pszName,cbName);
     pb += cbName;

     pb = memcpy(pb,pprt->pszDriver,cbDriver);
     pb += cbDriver;

     pb = memcpy(pb,pprt->pszModel,cbModel);
     pb += cbModel;

     memcpy(pb,pprt->pbDriverData,cbData);

     *pcbData = cb;                        // Return actual size
     return TRUE;
 }


 /***        PrintChangeJobProperties - Let user change printer job properties
  *
  *        Entry
  *            hprt    - printer handle
  *
  *        Exit-Success
  *            Returns TRUE, job properties changed
  *
  *        Exit-Failure
  *            Returns FALSE, job properties not changed
  */
 BOOL PrintChangeJobProperties(HPRINTER hprt)
 {
     HAB         hab;
     BYTE *        pb;
     PPRINTER        pprt = hprt;
     char *        pszName;
     LONG        rc;

     MemAssert(pprt);
     MemAssert(pprt->pprtlist);
     hab = pprt->pprtlist->hab;

     GetDriverData(pprt);                // Try to get driver data

     if (pprt->pbDriverData != NULL) {        // We do have it
         pb = pprt->pbDriverData;        //  Use our data as starting point
         pszName = NULL;                 // Do not use printer name
     }
     else {
         pb = NULL;                        // We have no data
         pszName = pprt->pszName;        // Tell driver to get it from printer
     }

     rc = DevPostDeviceModes(
             hab,                        // Anchor block
             (VOID *)pb,                 // Buffer for data
             pprt->pszDriver,                // Driver name
             pprt->pszModel,                // Device name
             pszName,                        // Printer name
             DPDM_POSTJOBPROP                // Option
     );
     return (rc == DEV_OK);
 }


 /***        PrintResetJobProperties - Reset printer to default job properties
  *
  *        Entry
  *            hprt - printer handle
  *
  *        Exit
  *            Job properties set to default for printer
  */
 VOID PrintResetJobProperties(HPRINTER hprt)
 {
     HAB         hab;
     PPRINTER        pprt = hprt;

     MemAssert(pprt);
     MemAssert(pprt->pprtlist);
     hab = pprt->pprtlist->hab;

     // Free properties, if present

     if (pprt->pbDriverData != NULL) {
         MemFree(pprt->pbDriverData);
         pprt->pbDriverData = NULL;
         pprt->cbDriverData = 0;
     }

     // Get job properties

     GetDriverData(pprt);
 }


 /***        PrintMatchPrinter - Select printer that matches job properties
  *
  *        Entry
  *            hprtlist - printer list
  *            cbData   - size of pb buffer in bytes
  *            pbData   - buffer with job properties
  *
  *        Exit-Success
  *            Returns valid HPRINTER that matches Job Properties
  *            Supplied job properties are set for printer.
  *
  *        Exit-Failure
  *            Returns NULL, no matching printer
  *                Caller should use default printer (PrintQueryDefaultPrinter
  */
 HPRINTER PrintMatchPrinter(HPRINTERLIST hprtlist,USHORT cbData,BYTE *pbData)
 {
     ULONG            cb;
     HAB             hab;
     BYTE *            pb;                 // Driver data
     PJOBPROP            pjp;
     PPRINTER            pprt;
     PPRINTER            pprtMatch;
     PPRINTERLIST    pprtlist = hprtlist;
     char *            pszDriver;                // Driver name
     char *            pszModel;                // Model name
     char *            pszName;                // Printer name

     MemAssert(pprtlist);
     hab = pprtlist->hab;

     // Buffer must be at least as large as header

     if (cbData < sizeof(JOBPROP))        // Too small
         return NULL;                        //  Fail

     // Check internal length

     pjp = (PJOBPROP)pbData;                // Point at header
     if (pjp->cb != cbData)                // Internal length does not match
         return NULL;                        //  Fail

     // Check internal counts

     cb = pjp->cbName+pjp->cbDriver+pjp->cbModel+pjp->cbData+sizeof(JOBPROP);
     if (cb > (ULONG)USHRT_MAX)                // Numbers too large
         return NULL;
     if ((USHORT)cb != cbData)                // Lengths do not match
         return NULL;

     // Set string/data pointers

     pszName = (BYTE *)pjp+sizeof(JOBPROP); // Name
     pszDriver = pszName + pjp->cbName;           // Driver
     pszModel = pszDriver + pjp->cbDriver;  // Model
     pb = pszModel + pjp->cbModel;           // Data

     // Try to find close match
     //
     // If we find a printer that matches Name, Driver, Model, and driver
     // data size, we have an exact match, and return that.
     //
     // If we find a printer that matches all but the Name, we return the
     // first such printer (after examining all printers to make sure there
     // is no exact match.
     //
     // Otherwise, we return NULL.

     pprtMatch = NULL;                        // No match, yet
     for (pprt=pprtlist->pprtHead; pprt!=NULL; pprt=pprt->pprtNext) {
         if ( (stricmp(pprt->pszDriver,pszDriver) == 0) &&
              (stricmp(pprt->pszModel,pszModel) == 0) ) { // Match Driver.Mode

             cb = DevPostDeviceModes(        // Get driver data size
                     hab,                // Anchor block
                     NULL,                // Buffer for data (NULL returns siz
                     pprt->pszDriver,        // Driver name
                     pprt->pszModel,        // Device name
                     NULL,                // Printer name
                     DPDM_QUERYJOBPROP        // Option
             );

             if (cb == pjp->cbData) {        // Match driver data size
                 if (stricmp(pprt->pszName,pszName) == 0) { // Exact match?
                     pprtMatch = pprt;        // Exact match
                     break;                // Exit loop
                 }
                 else if (pprtMatch == NULL) // First close match
                     pprtMatch = pprt;        // Remember close match
             }
         }
     }

     // Update job properties in printer

     if (pprtMatch != NULL) {                // Found a match
         if (pprt->pbDriverData == NULL) // No buffer, yet
             pprt->pbDriverData = MemAlloc(pjp->cbData); // Alloc buffer

         if (pprt->pbDriverData != NULL) { // Have buffer
             memcpy(pprt->pbDriverData,pb,pjp->cbData); // Save job properties
             pprt->cbDriverData = pjp->cbData; // Save JP size
         }
     }
     return pprtMatch;                        // Return best match
 }


 //************************************************************************
 //
 //  PRIVATE Functions
 //
 //************************************************************************


 /***        MatchPrinter - Look for printer in list
  *
  *        Entry
  *            pprtlist   - Printer list
  *            pszQueue   - Queue name to match (may be NULL)
  *            pszPrinter - Printer name to match (may be NULL)
  *
  *        Exit-Success
  *            Returns TRUE
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 PPRINTER MatchPrinter(PPRINTERLIST pprtlist,char *pszQueue,char *pszPrinter)
 {
     PPRINTER        pprt;

     for (pprt=pprtlist->pprtHead; pprt!=NULL; pprt=pprt->pprtNext) {
         if ((pprt->lType == OD_QUEUED) && (pszQueue != NULL)) { // A queue
             if (strcmp(pprt->pszLogAddr,pszQueue) == 0)
                 return pprt;
         }
         else if (pszPrinter != NULL) {        // A direct printer
             if (strcmp(pprt->pszPrinter,pszPrinter) == 0)
                 return pprt;
         }
     }
     return NULL;
 }


 /***        AddPrinter - Add printer to printer list (if unique)
  *
  *        NOTE: If the printer is not added, it is freed!
  *
  *        Entry
  *            pprtlist - printer list
  *            pprt     - printer to be added
  *
  *        Exit-Success
  *            Returns TRUE
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 BOOL AddPrinter(PPRINTERLIST pprtlist,PPRINTER pprtNew)
 {
     PPRINTER        pprt;

     // Avoid duplicate entry

     if (pprtNew->lType == OD_QUEUED) {        // Queue, check for duplicate
         pprt = MatchPrinter(pprtlist,pprtNew->pszLogAddr,NULL);
         if (pprt != NULL) {                // Found a match
             pprt->fMultipleDrivers = TRUE; // Force query of driver/model
             DestroyPrinter(pprtNew);        // No need for new printer
             return TRUE;                // Do not add printer
         }
     }
     else {                                // Direct printer
         // Do nothing, since Print Manager/Spooler never produce
         // duplicate printers.
         ;                                // Do nothing
     }

     // Link onto list

     pprtNew->pprtPrev = pprtlist->pprtTail; // Set previous printer
     if (pprtlist->pprtHead == NULL)        // List is empty
         pprtlist->pprtHead = pprtNew;        // Put at head of list
     else
         pprtlist->pprtTail->pprtNext = pprtNew; // Add at end of list
     pprtlist->pprtTail = pprtNew;        // New tail of list
     pprtNew->pprtNext = NULL;                // Set next printer

     pprtNew->pprtlist = pprtlist;        // Point at owning list
     return TRUE;
 }


 /***        SplitPrinter - Split printer if it contains multiple queue names
  *
  *        Entry
  *            pprtlist - printer list
  *            pprt     - printer to be split
  *
  *        Exit
  *            Returns next PPRINTER
  */
 PPRINTER SplitPrinter(PPRINTERLIST pprtlist,PPRINTER pprtOld)
 {
     PPRINTER        pprt;
     PPRINTER        pprtNext;
     PPRINTER        pprtPrev;
     char *        psz;
     char *        psz1;
     USHORT        cprtAdded=0;

     if (pprtOld->lType != OD_QUEUED)        // If not a queue
         return pprtOld->pprtNext;        //  Next printer

     psz = strpbrk(pprtOld->pszLogAddr,","); // Find queue name separator
     if (psz == NULL)                        // If no separator
         return pprtOld->pprtNext;        //  Next printer

     // Split up the printer

     strcpy(achPath,pprtOld->pszLogAddr); // Get working copy of queue name
     psz = achPath;
     while (psz != NULL) {
         psz1 = strpbrk(psz,",");        // Find separator
         if (psz1 != NULL) {                // Found one
             *psz1 = '\0';                // Terminate this queue name
             psz1++;                        // Advance to next queue name
         }
         pprt = ClonePrinter(pprtOld,psz); // Clone with different pszLogAddr
         if (pprt == NULL)                // Clone failed
             psz = NULL;                 // Exit loop
         else {                                // Clone successful
             AddPrinter(pprtlist,pprt);        // Add new printer
             cprtAdded++;                // Printer (may have been) added
             psz = psz1;                 // Advance to next queue (if any)
             // Note: AddPrinter may not have actually added the printer,
             //             because it the split printer may have been a dupli
             //             That is okay, though, because we use cprtAdded onl
             //             to know if we can delete pprtOld.
         }
     }

     // Delete old printer, if we were able to split it
     //
     // If we were not able to clone the printer at all, then we leave
     // it in the list, since the queue list was truncated to the first
     // queue name!
     //
     // The normal case, however, is that we did split it successfully,
     // so we want to delete the old printer.
     //
     // NOTE: If we were guaranteed that one of the AddPrinter calls actually
     //             added a printer, we would *know* that pprtNext would never
     //             NULL.  However, it is possible that a split could result i
     //             printers that are *all* already in the list.  Besides, the
     //             code is more robust this way.

     pprtNext = pprtOld->pprtNext;        // Next printer
     if (cprtAdded != 0) {                // Have to delete old printer
         pprtPrev = pprtOld->pprtPrev;        // Previous printer
         if (pprtNext != NULL)                // There is a following printer
             pprtNext->pprtPrev = pprtPrev; // Set back link
         if (pprtPrev != NULL)                // There is a preceding printer
             pprtPrev->pprtNext = pprtNext; // Set forward link
         DestroyPrinter(pprtOld);        // No need for this anymore
     }

     return pprtNext;                        // Next printer
 }


 /***        ClonePrinter - Create a duplicate PRINTER with a different pszLog
  *
  *        Entry
  *            pprt       - printer to clone
  *            pszLogAddr - logical address to set (QUEUE1)
  *
  *        Exit-Success
  *            Returns PPRINTER
  *
  *        Exit-Failure
  *            Returns NULL
  */
 PPRINTER ClonePrinter(PPRINTER pprtOriginal,char *pszLogAddr)
 {
     PPRINTER        pprt;

     pprt = CreatePrinter(
                 pprtOriginal->lType,                // Copy type
                 pszLogAddr,                        // New logical address
                 pprtOriginal->pszPrinter,        // Copy printer
                 pprtOriginal->pszDriver,        // Copy driver
                 pprtOriginal->pszModel,         // Copy model
                 pprtOriginal->fMultipleDrivers        // Copy flag
            );
     return pprt;
 }


 /***        GetDriverModel - Get Driver and Model for a printer
  *
  *        Entry
  *            pprt - printer
  *
  *        Exit
  *            If printer was a queue, and the existing printer/model may
  *            not be the one the user setup, get it from the HINI_SYSTEM.
  *
  *        Uses
  *            achPath
  *
  *        NOTE 1:
  *            We *need* to query the PM_SPOOLER_QUEUE_DD section *only* if th
  *            queue might have more than one driver associated with it.  This
  *            could happen in several cases:
  *
  *                1)  If the queue is associated with a single printer, but
  *                        that printer has more than one driver/model pair.
  *
  *                2)  If the queue is associated with more than one printer,
  *                        and the set of driver/model pairs supported by
  *                        these printers has more than one member.
  *
  *            (1) Is easy to compute (we keep a flag when we build the PRINTE
  *                object).
  *
  *            (2) Is more complicated, so we compute a cheaper version:
  *
  *                2a) If the queue is associated with more than one printer.
  *
  *                We compute this in AddPrinter.        If AddPrinter tries t
  *                a PRINTER that has a pszLogAddr that matches one already
  *                added, then it knows we have a queue pointing to more than
  *                one printer.  So, it sets the fMultipleDrivers flag, and th
  *                the code below can use that single flag to determine if it
  *                has to get the driver/model from HINI_SYSTEM).
  *
  *        NOTE 2:
  *             For an OD_DIRECT printer (no queue) there is no way to figure
  *             out which driver/model pair to use, so we take the first one
  *             in the list!  Since that is what we did when we built the
  *             printer object initialy, we have nothing to do.
  */
 VOID GetDriverModel(PPRINTER pprt)
 {
     LONG    cb;
     char *  psz;
     char *  pszDriver;
     char *  pszModel;


     if (pprt->lType != OD_QUEUED)        // Not a queue
         return;                         //  Driver.model are correct

     if (!pprt->fMultipleDrivers)        // Only one driver
         return;                         //  No change needed

     // We have a queue with multiple driver.model choices

     pprt->fMultipleDrivers = FALSE;        // Only do this computation once

     // Get driver.model for this queue

     cb = PrfQueryProfileString(
             HINI_SYSTEM,                // system ini
             "PM_SPOOLER_QUEUE_DD",        // app section
             pprt->pszName,                // printer name
             "",                         // Default value
             achPath,                        // Buffer to receive value
             (ULONG)sizeof(achPath)        // Buffer size
     );
     if (cb <= 1)                        // No driver.model
         return;                         // Use existing one

     // Parse driver.model string

     psz = achPath;
     // A: "IBM4201;"
     //           ^
     // B: "LASERJET.HP LaserJet IID;"
     //           ^

     // Parse the driver and model names

     pszDriver = psz;                        // Set driver name
     psz = strpbrk(psz,".;");                // Find separator
     if (psz == NULL)                        // Bad format
         return;                         //  Exit without changing
     if (*psz == '.') {                        // Found model name separator
         *psz = '\0';                        // Null terminate driver name
         pszModel = psz + 1;                // Set model name
         psz = strpbrk(pszModel,";");        // Find end of model name
         if (psz == NULL)                // Bad format
             return;                        //  Exit without changing
     }
     else                                // No model name
         pszModel = pszDriver;                // Make model same as driver nam
     *psz = '\0';                        // Null terminate driver name

     // Create string copies

     pszDriver = MemStrDup(pszDriver);
     if (pszDriver == NULL)
         return;

     pszModel = MemStrDup(pszModel);
     if (pszModel == NULL) {
         MemFree(pszDriver);
         return;
     }

     // Update printer structure

     if (pprt->pszDriver != NULL)
         MemFree(pprt->pszDriver);
     if (pprt->pszModel != NULL)
         MemFree(pprt->pszModel);

     pprt->pszDriver = pszDriver;
     pprt->pszModel = pszModel;
 }


 /***        GetDescription - Get description from HINI_SYSTEM
  *
  *        Get printer description or queue description, as appropriate.
  *
  *        Entry
  *            pprt - printer
  *
  *        Exit
  *            Set description from HINI_SYSTEM.
  *
  *        Uses
  *            achPath
  */
 VOID GetDescription(PPRINTER pprt)
 {
     LONG    cb;
     char *  psz;
     char *  pszApp;

     if (pprt->lType == OD_QUEUED)    // Queue
         pszApp = "PM_SPOOLER_QUEUE_DESCR";
     else
         pszApp = "PM_SPOOLER_PRINTER_DESCR";

     cb = PrfQueryProfileString(
             HINI_SYSTEM,                // System ini
             pszApp,                        // App
             pprt->pszName,                // Queue/printer name
             "",                         // Default value
             achPath,                        // Buffer to receive value
             (ULONG)sizeof(achPath)        // Buffer size
     );
     if (cb <= 1)                        // No description
         return;                         // Use existing one

     // Parse description

     psz = achPath;
     psz = strpbrk(psz,";");                // Find end of description
     if (psz == NULL)                        // Bad format
         return;                         //  Exit without changing
     *psz = '\0';                        // Null terminate desription

     // Duplicate description

     psz = MemStrDup(achPath);
     if (psz == NULL)
         return;

     if (pprt->pszDescription != NULL)
         MemFree(pprt->pszDescription);
     pprt->pszDescription = psz;
 }


 /***        QueryDefaultPrinterQueue - Get the default printer and queue
  *
  *        Entry
  *            ppszQueue        - pointer to receive pointer to queue name
  *            ppszPrinter - pointer to receive pointer to printer name
  *
  *        Exit
  *            One or both of ppszQueue/ppszPrinter may be set to NULL!
  *            Caller must call MemFree on ppszQueue/Printer!
  *
  *        Uses
  *            achPath
  */
 VOID QueryDefaultPrinterQueue(char **ppszQueue,char **ppszPrinter)
 {
     ULONG   cb;

     // Get default printer and queue

     cb = PrfQueryProfileString(
             HINI_USER,                        // OS2.INI
             "PM_SPOOLER",                // Application
             "PRINTER",                        // Variable
             "",                         // Default value
             achPath,                        // Buffer to receive value
             (ULONG)sizeof(achPath)        // Buffer size
          );
     if (cb == 0)                        // Couldn't get value
         *ppszPrinter = NULL;
     else {
         TrimTrailingSemicolon(achPath); // Get rid of trailing ";"
         *ppszPrinter = MemStrDup(achPath);
     }

     cb = PrfQueryProfileString(
             HINI_USER,                        // OS2.INI
             "PM_SPOOLER",                // Application
             "QUEUE",                        // Variable
             "",                         // Default value
             achPath,                        // Buffer to receive value
             (ULONG)sizeof(achPath)        // Buffer size
          );
     if (cb == 0)
         *ppszQueue = NULL;                // Couldn't get value
     else {
         TrimTrailingSemicolon(achPath); // Get rid of trailing ";"
         *ppszQueue = MemStrDup(achPath);
     }
 }


 /***        TrimTrailingSemicolon - truncate string at first semicolon
  *
  *        Entry
  *            psz - string to truncate
  *        Exit
  *            String truncated at first semicolon (if any)
  */
 void TrimTrailingSemicolon(char *psz)
 {
     while ((*psz != '\0') && (*psz != ';'))
         psz++;
     *psz = '\0';
 }


 /***        PrinterBeginSearch - Start enumeration of installed printers
  *
  *        Entry
  *            ppprt - pointer to receive pointer to printer
  *
  *        Exit-Success
  *            Returns search handle, ppprt filled in
  *
  *        Exit-Failure
  *            Returns NULL
  */
 PSEARCHP PrinterBeginSearch(PPRINTER *ppprt)
 {
     ULONG        cb;
     ULONG        cb1;
     BOOL        f;
     PSEARCHP        psrchp;

     // Get size of buffer for printer names

     f = PrfQueryProfileSize(
             HINI_SYSTEM,                // Only look in system profile
             "PM_SPOOLER_PRINTER",        // Pointer to application name
             NULL,                        // Pointer to keyname
             &cb                         // Pointer to return buffer size
             );
     if (!f)
         return NULL;                        // Failed

     // Create search structure

     psrchp = MemAlloc(sizeof(SEARCHP));
     if (psrchp == NULL)
         return NULL;
     psrchp->pchNext = NULL;

     // Allocate buffer for names

     psrchp->pchName = MemAlloc((USHORT)cb);
     if (psrchp->pchName == NULL) {
         PrinterEndSearch(psrchp);        // Frees search structure
         return NULL;                        // Failed
     }

     // Get all printer names

     cb1 = PrfQueryProfileString(
             HINI_SYSTEM,                // Only look in system profile
             "PM_SPOOLER_PRINTER",        // Application name
             NULL,                        // Keyname
             "",                         // Default value
             psrchp->pchName,                // Name list to be returned
             cb                                // Buffer size
             );

     if (cb1 == 0) {                        // No printers installed
         PrinterEndSearch(psrchp);
         return NULL;
     }

     // Get first printer

     psrchp->pchNext = psrchp->pchName;        // Start at first printer
     *ppprt = PrinterNextSearch(psrchp); // Get a printer
     if (*ppprt != NULL)                 // Found one
         return psrchp;                        //  Return success
     else {
         PrinterEndSearch(psrchp);        // No printer found
         return NULL;                        //  Return failure
     }
 }


 /***        PrinterNextSearch - Continue enumeration of installed printers
  *
  *        Entry
  *            psrchp - search handle from PrinterBeginSearch
  *
  *        Exit-Success
  *            Returns PPRINTER
  *
  *        Exit-Failure
  *            Returns NULL
  */
 PPRINTER PrinterNextSearch(PSEARCHP psrchp)
 {
     PPRINTER        pprt=NULL;

     // Scan printers until we find a valid one, or the list is exhausted
     while ((*psrchp->pchNext != '\0') && (pprt == NULL)) {
         pprt = GetPrinterData(psrchp->pchNext); // Get printer
         psrchp->pchNext += strlen(psrchp->pchNext)+1; // Next printer
     }
     return pprt;
 }


 /***        PrinterEndSearch - End enumeration of installed printer drivers
  *
  *        Entry
  *            psearchp - search handle from PrinterBeginSearch
  *
  *        Exit-Success
  *            Returns TRUE
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 BOOL PrinterEndSearch(PSEARCHP psrchp)
 {
     if (psrchp == NULL)
         return TRUE;

     if (psrchp->pchName != NULL)
         MemFree(psrchp->pchName);
     MemFree(psrchp);
     return TRUE;
 }


 /***        GetPrinterData - Get printer data
  *
  *        Entry
  *            achPrinter - printer name
  *
  *        Exit-Success
  *            returns PPRINTER
  *
  *            NOTE 1: if lType == OD_QUEUED, then pprt->pszLogAddr is one or
  *                    more queue names (separated by ",").
  *
  *                    The caller should clone the PRINTER structure as many
  *                    times as necessary so that there is one for each queue.
  *
  *            NOTE 2: pprt->pszDescription is not filled in.
  *
  *                    After the caller solves Note 1, the description should
  *                    be retrieved from OS2.INI.        Use PM_SPOOLER_QUEUE_
  *                    for a queue, and PM_SPOOLER_PRINTER_DESCR for a port
  *                    (both in HINI_SYSTEM)
  *
  *            NOTE 3: pprt->pszDriver/pszModel may not be correct
  *
  *                    After the caller solves Note 1, for each queue the
  *                    driver/model should be retrieved from PM_SPOOLER_QUEUE_
  *                    (in HINI_SYSTEM).
  *
  *        Exit-Failure
  *            Returns NULL
  *
  *        Uses
  *            achPath
  */
 PPRINTER GetPrinterData(char *pszPrinter)
 {
     USHORT        cb;
     BOOL        fComma;                 // True if multiple drivers
     ULONG        lType;
     PPRINTER        pprt;
     char *        psz;
     char *        pszDriver;
     char *        pszLogAddr;
     char *        pszModel;
     char *        pszQueue;

     // Get the printer details
     cb = (USHORT)PrfQueryProfileString(
             HINI_SYSTEM,                // Only look in system profile
             "PM_SPOOLER_PRINTER",        // Application name
             pszPrinter,                 // Keyname
             "",                         // Default value
             achPath,                        // Buffer to receive value
             (ULONG)sizeof(achPath)        // Buffer size
          );

     if (cb == 0)
         return NULL;

     // Get the logical address name, driver name, model name, and queue name

     psz = achPath;

     // Parse off logical address (usually a port name)
     //
     // A: "LPT1;LASERJET.HP LaserJet IID;QUEUE1;;"
     //           ^
     // B: "LPT1;LASERJET.HP LaserJet IID,PSCRIPT;QUEUE1,QUEUE2;;"
     //           ^

     pszLogAddr = achPath;                // Set logical address
     psz = strchr(psz,';');                // Find end of log addr
     if (psz == NULL)                        // Bad format
         return NULL;                        //  FAIL

     *psz = '\0';                        // Null terminate log addr
     psz++;                                // Skip separator

     // Parse off driver name and model
     //
     // NOTE:  These are provisional.  For a print queue, the driver/model
     //              have to be retrieved (by the caller) from PM_SPOOLER_QUEU
     //              in HINI_SYSTEM.
     //
     // A: "LPT1_LASERJET.HP LaserJet IID;QUEUE1;;"
     //                ^
     // B: "LPT1_LASERJET.HP LaserJet IID,POSTSCRIPT;QUEUE1,QUEUE2;;"
     //                ^

     pszDriver = psz;                        // Set driver name
     psz = strpbrk(psz,".,;");                // Find separator
     if (psz == NULL)                        // Bad format
         return NULL;                        //  FAIL
     fComma = FALSE;                        // Assume no comma
     if (*psz == '.') {                        // Found model name separator
         *psz = '\0';                        // Null terminate driver name
         pszModel = psz + 1;                // Set model name
         psz = strpbrk(pszModel,",;");        // Find end of model name
         if (psz == NULL)                // Bad format
             return NULL;                //  FAIL
     }
     else if (*psz == ',' || *psz == ';') // No model name
         pszModel = pszDriver;                // Make model same as driver nam

     // A: "LPT1_LASERJET_HP LaserJet IID;QUEUE1;;"
     //                                        ^
     // B: "LPT1_LASERJET_HP LaserJet IID,POSTSCRIPT;QUEUE1,QUEUE2;;"
     //                                        ^
     if (*psz == ',')                        // More drivers, remember to skip
         fComma = TRUE;
     *psz = '\0';                        // Null terminate driver name
     psz++;                                // Skip separator

     // A: "LPT1_LASERJET_HP LaserJet IID_QUEUE1;;"
     //                                         ^
     // B: "LPT1_LASERJET_HP LaserJet IID_POSTSCRIPT;QUEUE1,QUEUE2;;"
     //                                         ^
     if (fComma) {                        // Need to skip rest of field
         psz = strpbrk(psz,";");         // Skip to end of driver field
         if (psz == NULL)                // Bad format
             return NULL;                //  FAIL
         psz++;                                // Skip separator
     }

     // A: "LPT1_LASERJET_HP LaserJet IID_QUEUE1;;"
     //                                                ^
     // B: "LPT1_LASERJET_HP LaserJet IID_POSTSCRIPT;QUEUE1,QUEUE2;;"
     //                                                    ^
     // NOTE: We leave the list of queues, so that caller can enumerate
     //             all queues!
     //
     if (*psz == ';')                        // No queue
         pszQueue = NULL;
     else {                                // Queue present
         pszQueue = psz;                 // Set queue name
         psz = strpbrk(psz,";");         // Find end of queue name(s)!
         if (psz == NULL)                // Bad format
             return NULL;                //  FAIL
         *psz = '\0';                        // Null terminate queue name
     }

     // Figure out whether this printer is queued or not

     if (pszQueue != NULL) {                // Printer is queued
         lType = OD_QUEUED;
         psz = pszQueue;
     }
     else {                                // Printer is direct
         lType = OD_DIRECT;
         psz = pszLogAddr;
     }

     // Create printer structure

     pprt = CreatePrinter(
             lType,                        // Set type
             psz,                        // Set logical address
             pszPrinter,                 // Set printer
             pszDriver,                        // Set driver
             pszModel,                        // Set model
             fComma                        // Set flag
        );
     return pprt;
 }


 /***        CreatePrinter - Create a PRINTER
  *
  *        Entry
  *            lType      - printer type (OD_DIRECT or OD_QUEUED)
  *            pszLogAddr - logical address (LPT1 or QUEUE1)
  *            pszPrinter - printer name
  *            pszDriver  - driver name
  *            pszModel   - model name
  *
  *        Exit-Success
  *            Returns PPRINTER
  *
  *        Exit-Failure
  *            Returns NULL
  */
 PPRINTER CreatePrinter(LONG lType,
                        char *pszLogAddr,
                        char *pszPrinter,
                        char *pszDriver,
                        char *pszModel,
                        BOOL fMultipleDrivers)
 {
     PPRINTER        pprt;

     pprt = MemAlloc(sizeof(PRINTER));
     if (pprt == NULL)
         return NULL;

     // Create copies of strings to complete printer structure

     pprt->pszLogAddr = MemStrDup(pszLogAddr); // Duplicate logical address
     pprt->pszPrinter = MemStrDup(pszPrinter); // Duplicate printer name
     pprt->pszDriver  = MemStrDup(pszDriver);  // Duplicate driver
     pprt->pszModel   = MemStrDup(pszModel);  // Duplicate Model

     // Set other fields

     pprt->fMultipleDrivers = fMultipleDrivers; // Set flag
     pprt->lType = lType;                // Set type
     pprt->pszDescription = NULL;        // No description, yet
     pprt->cbDriverData = 0;                // No job properites
     pprt->pbDriverData = NULL;                // No job properites

     // Set consistent printer "name"
     if (lType == OD_QUEUED)
         pprt->pszName = pprt->pszLogAddr; // Use queue name
     else
         pprt->pszName = pprt->pszPrinter; // Use printer name

     // Make sure copies succeeded

     if ((pprt->pszLogAddr     == NULL) || // One or more dups failed
         (pprt->pszDriver      == NULL) ||
         (pprt->pszModel       == NULL) ||
         (pprt->pszPrinter     == NULL)) {
         DestroyPrinter(pprt);                // Destroy printer
         pprt = NULL;
     }
     return pprt;
 }


 /***        DestroyPrinter - Destroy a PRINTER
  *
  *        Entry
  *            pprt - printer to destroy
  *
  *        Exit
  *            NONE
  */
 VOID DestroyPrinter(PPRINTER pprt)
 {
     MemAssert(pprt);
     if (pprt->pszLogAddr != NULL)
         MemFree(pprt->pszLogAddr);
     if (pprt->pszDriver != NULL)
         MemFree(pprt->pszDriver);
     if (pprt->pszModel != NULL)
         MemFree(pprt->pszModel);
     if (pprt->pszPrinter != NULL)
         MemFree(pprt->pszPrinter);
     if (pprt->pszDescription != NULL)
         MemFree(pprt->pszDescription);
     if (pprt->pbDriverData != NULL)
         MemFree(pprt->pbDriverData);
     MemFree(pprt);
 }


 /***        GetDriverData - Get driver data for printer (if not already set)
  *
  *        This function attempts to set the Job Properties for a printer
  *        by querying the driver.  If successful, the Job Properties are
  *        stored in the printer.        Otherwise, they are left as NULL, and
  *        the driver will use its own default job properties.
  *
  *        Entry
  *            pprt - printer
  *
  *        Exit
  *            pprt->pbDriverData set to driver data, if at all possible.
  */
 VOID GetDriverData(PPRINTER pprt)
 {
     HAB     hab;
     LONG    cb;
     BYTE *  pb;
     LONG    rc;

     MemAssert(pprt);
     MemAssert(pprt->pprtlist);
     hab = pprt->pprtlist->hab;

     // See if job properties already set

     if (pprt->pbDriverData != NULL)        // They are
         return;                         //  Nothing to do

     // Assume no job properties

     pprt->cbDriverData = 0;
     pprt->pbDriverData = NULL;

     // Get size

     cb =  DevPostDeviceModes(
             hab,                        // Anchor block
             NULL,                        // Buffer for data (NULL returns siz
             pprt->pszDriver,                // Driver name
             pprt->pszModel,                // Device name
             pprt->pszPrinter,                // Printer name
             DPDM_QUERYJOBPROP                // Option
     );

     if (cb == DPDM_NONE)                // No job properties available
         return;                         //  Do nothing

     if (cb == DPDM_ERROR)                // Could not get job properties
         return;                         //  Do nothing

     pb = MemAlloc((USHORT)cb);                // Allocate buffer
     if (pb == NULL)                        // Could not allocate it
         return;                         //  Nothing to do

     // Made it this far, get driver data

     rc = DevPostDeviceModes(
             hab,                        // Anchor block
             (VOID *)pb,                 // Buffer for data
             pprt->pszDriver,                // Driver name
             pprt->pszModel,                // Device name
             pprt->pszPrinter,                // Printer name
             DPDM_QUERYJOBPROP                // Option
     );

     if (rc == DEV_OK) {
         pprt->cbDriverData = (USHORT)cb;
         pprt->pbDriverData = pb;
     }
     return;
 }


 QUEUES.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\QUEUES.C

 /***************************************************************************\
 * queues.c - Spy code to manager the queue list dialog
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define        INCL_WINDIALOGS
 #define        INCL_WINHEAP
 #define        INCL_WININPUT
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMESSAGEMGR
        INCL_WINPOINTERS                /* needed for spy.h */
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include "spyhook.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include "spy.h"

 #define SIZEOFWND   34
 #define MAXMSGBYTES 100

 /* File Global Variables */
 HMQ      hmqDlgDisp;
 SHORT   chmqWatched = 0;
 SHORT   chmqInList = 0;



 /* Function Prototypes */
 MRESULT EXPENTRY SpyQueuesDlgProc(HWND, USHORT, MPARAM, MPARAM);

 void    InitQueueList(VOID);
 void    DisplayQueueInfoForWindow(HWND, HWND);
 void    DisplayQueueInfo(HWND, HMQ);
 void    SelectQueueFromText(HWND hwndDlg);
 void    BuildQueueWatchList(void);



 /***************************************************************************\
 * MRESULT EXPENTRY SpyQueuesDlgProc(hwnd, msg, mp1, mp2)
 *
 * The Spy Windows Dialog procedure
 \***************************************************************************/
 MRESULT EXPENTRY SpyQueuesDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL        fSelect = TRUE;
     SHORT       cQueues;
     HWND        hwndPoint;
     HMQ         hmqDisp;
     HMQ         hmqItem;   /* from handle of list item */
     USHORT      iItemFocus; /* Index to item that has the focus */

     switch (msg) {

     case WM_INITDLG:
         /* Initialize the dialog items */
         hwndWindowLB = WinWindowFromID(hwnd, DID_WINDOWLIST);
         InitQueueList();
         hmqDlgDisp = NULL;
         fTrackingListBox = TRUE;
         break;

     case WM_CHAR:
         /*
          * Handle VK_ENTER and VK_NEWLINE if our Edit control has
          * the focus and it is a keydown
          */
         if (!(SHORT1FROMMP(mp1) & KC_KEYUP) &&
                 (SHORT1FROMMP(mp1) & KC_VIRTUALKEY) &&
                 ( (SHORT2FROMMP(mp2) == VK_ENTER) ||
                   (SHORT2FROMMP(mp2) == VK_NEWLINE) )) {


             if (WinQueryFocus(HWND_DESKTOP, FALSE) ==
                     WinWindowFromID(hwnd, DID_WHANDLE)) {
                 SelectQueueFromText(hwnd);
                 break;
             }
         }

         /* Normaly pass to dialog procedure to handle message */
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:
             BuildQueueWatchList();

         case DID_CANCEL:
             /* Now dismiss the dialog */
             WinDismissDlg(hwnd, SHORT1FROMMP(mp1));
             break;
         case DID_WUNSELALL:
             fSelect = FALSE;
         case DID_WSELALL:
             cQueues = SHORT1FROMMR( WinSendMsg(hwndWindowLB, LM_QUERYITEMCOUN
                 0L, 0L));

             fTrackingListBox = FALSE;
             while (cQueues) {
                 /* Loop through all windows, selecting or unselcting all */
                 WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)--cQueues,
                     (MPARAM)fSelect);
             }
             fTrackingListBox = TRUE;
             break;

         case DID_WSELMOUSE:
             /* Call function to track mouse, returns window handle */
             hwndPoint = HwndSelWinWithMouse(hwnd, DisplayQueueInfoForWindow);
             if (hwndPoint == NULL)
                 break;    /* No window to process */

             /*
              * Now find the window in the list, Make the item visible
              * and set the item as selected.
              */
             hmqDisp = (HMQ)WinQueryWindowULong(hwndPoint, QWL_HMQ);
             cQueues = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYITEMCOUNT
                     0L, 0L));

             while (cQueues) {
                 /*
                  * Loop through all windows until we wind the right
                  * one with the correct window handle
                  */
                 hmqItem = (HMQ)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                         (MPARAM)--cQueues, 0L);

                 if (hmqItem == hmqDisp) {
                     /* found the right item, move it to top */
                     WinSendMsg(hwndWindowLB, LM_SETTOPINDEX, (MPARAM)cQueues,
                     WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)cQueues,
                             (MPARAM)TRUE);
                     break;
                 }
             }
             break;
         }
         break;


     default:
         /*
          * Default is to see if the listbox has changed its focus
          * item number.  If it has, then we want to display the information
          * about the window that the listbox cursor is over.  There is no
          * legal way to do this, One approach appears to temporary set the
          * listbox to be a single selection listbox, then query its selection
          * and set it back into multiple selection mode.
          */
         if (fTrackingListBox && hwndWindowLB != NULL) {

             WinSetWindowBits(hwndWindowLB, QWL_STYLE, 0L, LS_MULTIPLESEL);
             iItemFocus = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYSELECT
                     (MPARAM)LIT_FIRST, 0L));
             WinSetWindowBits(hwndWindowLB, QWL_STYLE, LS_MULTIPLESEL,
                     LS_MULTIPLESEL);

             if (iItemFocus != iCurItemFocus) {
                 iCurItemFocus = iItemFocus;
                 if (iItemFocus != (USHORT)-1) {

                     hmqDisp = (HMQ)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDL
                             (MPARAM)iItemFocus, 0L);
                     DisplayQueueInfo(hwnd, hmqDisp);
                 }
             }
         }
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
     }
     return 0L;
 }




 /****************************************************************************
 * InitQueueList ()
 *
 * Builds the list of Queues displayed in the queues dialog
 \***************************************************************************/
 void InitQueueList()
 {
     HENUM       henum;
     char        szClassName[50];    /* Class name of window */
     HWND        hwnd;
     ULONG       ulHMQ;
     PID         pidWindow;
     TID         tidWindow;
     SHORT       item;
     HMQ         *ahmqWatched;
     HMQ         *phmqT;
     SHORT       chmqAlloc;
     SHORT       i;

     /*
      * We will enumerate through all of the OBJECT windows looking for
      * windows that have the  WC_QUEUE type.
      */
     if (chmqWatched > 0) {
         ahmqWatched = (HMQ *)WinAllocMem(hHeap, sizeof(HMQ) * chmqWatched);
         chmqAlloc = chmqWatched;
         if (ahmqWatched != NULL)
             chmqWatched = SpyGetQueueList(chmqWatched, ahmqWatched);
     } else
         ahmqWatched = NULL;

     henum = WinBeginEnumWindows(HWND_OBJECT);
     if (henum == NULL)
         return;

     chmqInList = 0;
     while ((hwnd = WinGetNextWindow(henum)) != NULL) {
         WinLockWindow(hwnd, FALSE);
         WinQueryClassName(hwnd, sizeof(szClassName),
             (PSZ)szClassName);

         if (strcmp(szClassName, "#32767") == 0) {
             /*
              * We found an HMQ, add it to our list, Do not add our own
              * HMQ to the list!.
              */
             ulHMQ = (ULONG)WinQueryWindowULong(hwnd, QWL_HMQ);
             if ((HMQ)ulHMQ != hmqSpy) {
                 WinQueryWindowProcess(hwnd, &pidWindow, &tidWindow);

                 /* Reuse szClassName */
                 sprintf(szClassName, "0x%04x (PID:%d TID:%d)",
                         (USHORT)ulHMQ, (SHORT)pidWindow, (SHORT)tidWindow);

                 item = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_INSERTITEM,
                         (MPARAM)LIT_END, (MPARAM)(PSZ)szClassName));

                 /* Set the item handle to the handle of the window */
                 WinSendMsg(hwndWindowLB, LM_SETITEMHANDLE, (MPARAM)item,
                         (MPARAM)ulHMQ);
                 chmqInList++;

                 /*
                  * Now see if we should select the item
                  */
                 if (phmqT = ahmqWatched) {
                     for (i = 0; i < chmqWatched; i++) {
                         if ((HMQ)ulHMQ == *phmqT++) {
                             WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)i
                                     (MPARAM)TRUE);
                             break;
                         }
                     }
                 }
             }
         }

     }

     WinEndEnumWindows(henum);

     if (ahmqWatched)
         WinFreeMem(hHeap, (char *)ahmqWatched, chmqAlloc * sizeof(HMQ));
 }



 /***************************************************************************\
 * DisplayQueueInfoForWindow(HWND hwndDialog, HWND hwnd)
 *
 * Displays the information about the selected window in the dialog
 \***************************************************************************/
 void DisplayQueueInfoForWindow(hwndDlg, hwndDisp)
 HWND            hwndDlg;
 HWND            hwndDisp;
 {
 HMQ             hmqDisp;

     /* simply get the HMQ for the window and call display function */
     hmqDisp = (HMQ)WinQueryWindowULong(hwndDisp, QWL_HMQ);
     DisplayQueueInfo(hwndDlg, hmqDisp);
 }




 /***************************************************************************\
 * DisplayQueueInfo(HWND hwndDialog, HWND hwnd)
 *
 * Displays the information about the selected window in the dialog
 \***************************************************************************/
 void DisplayQueueInfo(hwndDlg, hmqDisp)
 HWND            hwndDlg;
 HMQ             hmqDisp;
 {
     HWND        hwnd;
     HMQ         hmqWindow;
     HENUM       henum;
     char        szTemp[50];
     PID         pidWindow;
     TID         tidWindow;


     if (hmqDisp != hmqDlgDisp)
     {
         hmqDlgDisp = hmqDisp;

         /*
          * See if we can find a top level window for this HMQ
          */
         henum = WinBeginEnumWindows(HWND_DESKTOP);
         if (henum == NULL)
             return;

         while ((hwnd = WinGetNextWindow(henum)) != NULL) {
             WinLockWindow(hwnd, FALSE);
             hmqWindow = (HMQ)WinQueryWindowULong(hwnd, QWL_HMQ);

             if (hmqWindow == hmqDisp)
                 break;
         }
         WinEndEnumWindows(henum);


         if (hwnd == NULL) {
             /*
              * No top level windows found look for object window.
              */
             henum = WinBeginEnumWindows(HWND_OBJECT);
             if (henum == NULL)
                 return;

             while ((hwnd = WinGetNextWindow(henum)) != NULL) {
                 WinLockWindow(hwnd, FALSE);
                 hmqWindow = (HMQ)WinQueryWindowULong(hwnd, QWL_HMQ);

                 if (hmqWindow == hmqDisp)
                     break;
             }
             WinEndEnumWindows(henum);
             if (hwnd == NULL)
                 return;
         }

         /* output information to window now */
         sprintf(szTemp, "0x%04x", (SHORT)(LONG)hmqDisp);
         WinSetDlgItemText(hwndDlg, DID_WHMQ, (PSZ)szTemp);

         sprintf(szTemp, "0x%04x", (SHORT)(LONG)hwnd);
         WinSetDlgItemText(hwndDlg, DID_WHANDLE, (PSZ)szTemp);

         /*
          * Warning, we only query the text if the window is not an object
          * window.  If it is an object window, the message queue may not
          * be processing messages, which could hang us
          */
         if (WinIsChild(hwnd, HWND_OBJECT))
             szTemp[0] = '\0';   /* No text available */
         else
             WinQueryWindowText(hwnd, sizeof(szTemp), (PSZ)szTemp);
         WinSetDlgItemText(hwndDlg, DID_WTEXT, (PSZ)szTemp);

         WinQueryWindowProcess(hwnd, &pidWindow, &tidWindow);
         sprintf(szTemp, "%d", (SHORT)pidWindow);
         WinSetDlgItemText(hwndDlg, DID_WPID, (PSZ)szTemp);
         sprintf(szTemp, "%d", (SHORT)tidWindow);
         WinSetDlgItemText(hwndDlg, DID_WTID, (PSZ)szTemp);
     }
 }



 /***************************************************************************\
 * void SelectQueueFromText(hwndDlg)
 *
 *   Updates the text that is displayed in the message text line
 \***************************************************************************/
 void SelectQueueFromText(hwndDlg)
 HWND    hwndDlg;
 {
     char    szTemp[80];
     HMQ     hmqSelect;
     SHORT   cItems;
     SHORT   i;


     /* First get the edit text from the string */
     WinQueryDlgItemText(hwndDlg, DID_WHANDLE, sizeof(szTemp),
             (PSZ)szTemp);

     hmqSelect = (HMQ)UConvertStringToNum(szTemp);

     cItems = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYITEMCOUNT,
             0L, 0L));

     for (i=0; i < cItems; i++) {
         if ((HMQ)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                 (MPARAM)i, 0L) == hmqSelect)
             break;  /* found it */
     }

     if (i < cItems) {
         /*
          * found the hmq, bring to top, and select it
          */
         WinSendMsg(hwndWindowLB, LM_SETTOPINDEX,
                 MPFROMSHORT(i),  (MPARAM)0L);

         /* Always set it on */
         WinSendMsg(hwndWindowLB, LM_SELECTITEM,
                 MPFROMSHORT(i), MPFROMSHORT(TRUE));

     } else {
         WinAlarm(HWND_DESKTOP, WA_WARNING);
         WinSetDlgItemText(hwndDlg, DID_WHANDLE, (PSZ)"");
     }

 }




 /***************************************************************************\
 * BuildQueueWatchList()
 *
 * Updates the list of windows to be watched from the listbox
 \***************************************************************************/
 void BuildQueueWatchList(void)
 {

     USHORT  itemPrevious;
     USHORT  item;

     HMQ     *ahmqList;
     HMQ     *phmqT;


     ahmqList = (HMQ *)WinAllocMem(hHeap, sizeof(HMQ) * chmqInList);
     if ((phmqT = ahmqList) == NULL)
         return;     /* Error */


     /*
      * Simply loop through asking for the next selected item in the
      * list.  Make sure not to overrun our list.
      */
     itemPrevious = (USHORT)LIT_FIRST;
     chmqWatched = 0;

     while ((item = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYSELECTION,
             (MPARAM)itemPrevious, 0L))) != (USHORT)LIT_NONE) {
         /*
          * Get the items handle, which has the value of the window handle
          */
         *phmqT++ = (HMQ)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                 (MPARAM)item, 0L);
         chmqWatched++;

         itemPrevious = item;    /* Where to cointinue the search */
     }

     SpySetQueueList (chmqWatched, ahmqList);

     WinFreeMem(hHeap, (char *)ahmqList, sizeof(HMQ) * chmqInList);
 }



 SHARE.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SHARE\SHARE.C

 /*
  * This example illustrates the use of shared memory and ram semaphores
  * between separate processes.  One process reads and stores keystrokes
  * into a circular buffer in shared memory, and another reads the buffer
  * and writes to standard output. The output program throttles output to
  * one character per sec so the effect of buffering can be seen. Type
  * CONTROL-d to terminate.
  *
  * Note the mutual exclusion on access to the head and tail pointers of the
  * buffer and on clearing and setting the full and empty semaphores.  For a
  * discussion, see Peterson & Silberschatz, "Operating System Concepts",
  * 2nd ed., chapter 9.
  *
  * Be aware when running this that the system does keyboard buffering
  * underneath this program, so you can type ahead more than you might think.
  *
  * Needs shrchild.c as companion program.
  *
  * Compile as: cl -AL -G2 -Lp share.c
  *
  * Created by Microsoft Corp. 1986
  */

 #define INCL_DOSPROCESS
 #define INCL_SUB
 #define INCL_DOSMEMMGR

 #include <os2def.h>
 #include <bse.h>
 "share.h"                /* common declarations between processes */

        CHILD1PROG        "SHRCHILD.EXE"                /* child process */
        CTRLd                4                        /* termination character
 #define FBSZ                32

 KBDKEYINFO KeyData;            /* declared in subcalls.h */

 main()
 {
         SEL                Selector;
         RESULTCODES        childID;     /* child process id */
         unsigned        rc;                /* return code */
         char                c,
                         fbuf[FBSZ];        /* failing object buffer */
         struct ShareRec *SmemPtr;        /* shared memory pointer */

         /* allocate the shared memory segment */
         if (rc = DosAllocShrSeg( SHRSEGSIZE, (PSZ)SHRSEGNAME,
                                 &Selector ))  {
                 printf("alloc of shared memory failed, error: %d\n", rc);
                 DosExit(EXIT_PROCESS, 0);
         }

         /* Get a far pointer from a 16 bit selector */
         SmemPtr = (struct ShareRec *) MAKEP(Selector, 0);

         /* Initialize circular buffer flags */
         DosSemClear((HSEM)&(SmemPtr->fullsem));
         DosSemSet((HSEM)&(SmemPtr->emptysem));
         DosSemClear((HSEM)&(SmemPtr->mutexsem));
         SmemPtr->head = 0;
         SmemPtr->tail = 0;

         /* exec asynchronously the consumer process */
         if (rc = DosExecPgm(
                 (PCHAR) fbuf,                        /* ObjNameBuf
                 FBSZ,                                /* ObjNameLen
                 EXEC_ASYNC,                        /* AsyncTraceFlags
                 (PSZ) 0L,                        /* Argument Strings        *
                 (PSZ) 0L,                        /* Environment Strings
                 &childID,
                                                 /* ID & Termination Codes */
                 (PSZ) CHILD1PROG )) {                /* Program Filename

                 printf("exec of child process failed, error: %d\n", rc);
                 DosExit(EXIT_PROCESS, 0);
         }

         /* Here, we read chars from the keyboard, and put them in a */
         /* circular buffer in shared memory */

         KbdCharIn(&KeyData, 0, 0);        /* read character from keyboard */

         while((c = KeyData.chChar) != CTRLd) {

             /* block if buffer full */
             DosSemWait((HSEM)&(SmemPtr->fullsem), WAITFOREVER);

             /* mutual exclusion on buffer pointers and semaphores */
             DosSemRequest((HSEM)&(SmemPtr->mutexsem), WAITFOREVER);
             SmemPtr->CircBuffer[SmemPtr->head] = c;
             SmemPtr->head++;                    /* step pointer */
             SmemPtr->head %= CIRCBUFSIZE;   /* wrap at end  */
             if(BUFFUL(SmemPtr))
                 DosSemSet((HSEM)&(SmemPtr->fullsem)); /* indicate buf full
             DosSemClear((HSEM)&(SmemPtr->emptysem));  /* indicate buf !emtpy
             DosSemClear((HSEM)&(SmemPtr->mutexsem));


             KbdCharIn(&KeyData, 0, 0);        /* read character from keyboard
         }
         DosKillProcess( 1, childID.codeTerminate);        /* kill consumer */
         DosExit( EXIT_PROCESS, 0 );           /* exit, terminating all thread
 }


 SHRCHILD.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SHARE\SHRCHILD.C

 /*
  * This is the child process exec'd asynchronously from share.c.
  *
  * Created by Microsoft Corp. 1986
  */

 #define INCL_DOSMEMMGR
 #define INCL_DOSSEMAPHORES
 #define INCL_DOSFILEMGR
 #define INCL_DOSPROCESS

 #include <os2def.h>
 <bsedos.h>                /* necessary whenever doscalls are made */
 "share.h"                /* common shared memory declarations */

 main()
 {
         SEL                        Selector;
         unsigned                rc;                /* return code */
         USHORT                        nBytes;
         char                        c;
         struct ShareRec         *SmemPtr;        /* shared memory pointer */

         /* get selector to the shared memory segment defined in parent */

         if (rc = DosGetShrSeg( (PSZ) SHRSEGNAME,
                                 &Selector ))  {
                 DosExit(EXIT_PROCESS, 0);
         }

         /* Get a far pointer from a 16 bit selector */

         SmemPtr = (struct ShareRec *) MAKEP(Selector, 0);

         for (;;) {

             /* block if buffer empty */
             DosSemWait((HSEM)&(SmemPtr->emptysem), WAITFOREVER);

             /* mutual exclusion on buffer pointers and semaphores */
             DosSemRequest((HSEM)&(SmemPtr->mutexsem), WAITFOREVER);
             c = SmemPtr->CircBuffer[SmemPtr->tail];        /* get next char *
             SmemPtr->tail++;                    /* step pointer */
             SmemPtr->tail %= CIRCBUFSIZE;   /* wrap at end  */
             if (SmemPtr->tail == SmemPtr->head)
                 DosSemSet((HSEM)&(SmemPtr->emptysem)); /* indicate buf empty
             DosSemClear((HSEM)&(SmemPtr->fullsem));    /* indicate buf !full
             DosSemClear((HSEM)&(SmemPtr->mutexsem));

             DosWrite( 1, (PVOID) &c, 1, &nBytes );     /* display next char *
             DosSleep(1000L);        /* max 1 per sec to demo buffering & sems
         }
 }


 SNAP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SNAP\SNAP.C

 /*
     snap.c -- PM snapshot utility
     Created by Microsoft Corporation, 1989
 */
 #define                INCL_WIN
 #define                INCL_GPI
 #define                INCL_DEV
 #include        <os2.h>
 #include        "snap.h"

 /* Global variables */
 char szSnap[5];

 HAB    habSnap;
 HMQ    hmqSnap;
 HWND   hwndSnap, hwndSnapFrame;
 HWND   hwndNextClipViewer;
 HPS    hpsScr;
 HHEAP  hHeap;

 HPOINTER hptrSnap;
 HPOINTER hptrHand;
 HPOINTER hptrSelect;

 RECTL wrcRgn;                   /* holds coordinates of selected region. */
 RECTL rcScreen;                 /* rectangle for the screen for bounding */

 int AboutCount = 0;

 int  wSnapMode = IDM_WINDOW;    /* snap either selected window or region. */

 BOOL fSnapWnd = FALSE;  /* snap selected window? */
 BOOL fSnapRgn = FALSE;  /* snap selected region? */
 BOOL fSelect = FALSE;   /* in the process of selecting region? */
 BOOL fNCArea = FALSE;   /* exclude nonclient area of window in snap? */
 BOOL fHide = TRUE;      /* hide snap's window while snapping */


 /* Function prototypes */
 MRESULT EXPENTRY SnapWndProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY AboutWndProc(HWND, USHORT, MPARAM, MPARAM);
 MRESULT EXPENTRY SaveFileDlgProc(HWND, USHORT, MPARAM, MPARAM);
 HFILE OpenSaveFile(HWND);
 void SnapWindow(ULONG);
 BOOL SnapInit(void);
 void SnapPaint(HPS);
 void SnapRegion(HPS);
 void DrawRgn(HPS);
 void SortRect(PRECTL, PRECTL);
 void SaveBitmap(void);
 void SaveBitmap2(HFILE hFile, HBITMAP hbm);
 void convert(NPBYTE pbuf, USHORT cch, USHORT cch2);
 HDC CreateDC(PSZ, HDC);
 void Copy(NPBYTE, NPBYTE, USHORT);
 int cdecl main(void);


 /* Routines */
 int cdecl main(void)
 {
     QMSG msg;
     ULONG ctlData;

     if (!SnapInit())
         return(FALSE);

     ctlData = FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER |
             FCF_SYSMENU | FCF_MENU | FCF_ICON;
     hwndSnapFrame = WinCreateStdWindow(HWND_DESKTOP,
             FS_ICON, &ctlData,
             (PCH)szSnap, (PCH)szSnap,
             0L,
             (HMODULE) NULL, 1,
             (HWND far *)&hwndSnap);


     if (!hwndSnap)
         return(FALSE);

     WinSetWindowPos(hwndSnapFrame, 0, 0, 0, 200, 75, SWP_SIZE | SWP_MOVE);
     WinShowWindow(hwndSnapFrame, TRUE);

     WinSetFocus(HWND_DESKTOP, hwndSnap);
     WinQueryWindowRect(HWND_DESKTOP, &rcScreen);

     if (WinOpenClipbrd(habSnap)) {
         WinSetClipbrdViewer(habSnap, hwndSnap);
         WinCloseClipbrd(habSnap);
     }

     while (WinGetMsg(habSnap, (PQMSG)&msg, NULL, 0, 0)) {
         WinDispatchMsg(habSnap, (PQMSG)&msg);
     }

     WinDestroyPointer(hptrSnap);
     WinDestroyPointer(hptrHand);
     WinDestroyPointer(hptrSelect);

     WinDestroyWindow(hwndSnapFrame);
     WinDestroyHeap(hHeap);
     WinDestroyMsgQueue(hmqSnap);

     WinTerminate(habSnap);

     return 0;
 } /* end winmain */


 HDC CreateDC(lpszDriver, hdcCompat)
 PSZ lpszDriver;
 HDC hdcCompat;
 {
     struct {
         ULONG FAR *lpLogAddr;
         PSZ lpszDriver;
     } opendc;

     opendc.lpLogAddr = NULL;
     opendc.lpszDriver = lpszDriver;

     return((HDC)DevOpenDC(habSnap, OD_MEMORY, (PSZ)"*", 2L,
             (PDEVOPENDATA)&opendc, hdcCompat));
 }

 BOOL SnapInit(void)
 {
     /*
      * Initialize the HAB
      */
     habSnap = WinInitialize(0);

     hmqSnap = WinCreateMsgQueue(NULL, 0);

     WinLoadString(habSnap, (HMODULE) NULL, IDS_SNAP, sizeof(szSnap), (PCH)szS

     hptrSnap = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDR_PTR_SNAP);
     hptrHand = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDR_PTR_HAND);
     hptrSelect = WinLoadPointer(HWND_DESKTOP, (HMODULE)NULL, IDR_PTR_SELECT);

     if (!WinRegisterClass(habSnap, (PCH)szSnap, SnapWndProc, (ULONG)0, 0))
         return(FALSE);

     if ((hHeap = WinCreateHeap(0, 0, 0, 0, 0, 0)) == NULL)
         return(FALSE);
     /*
      * We assume an empty clipboard when we start.
      */
     WinOpenClipbrd(habSnap);
     WinEmptyClipbrd(habSnap);
     WinCloseClipbrd(habSnap);
 } /* end snapinit */


 MRESULT EXPENTRY SnapWndProc(hwnd, message, mp1, mp2)
 HWND    hwnd;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     HWND hwndMenu;
     HPS hps;
     RECTL wrcUpdate;
     POINTL wptTemp;

     switch (message) {
         case WM_BUTTON1DOWN:
             if (fSnapWnd) {
                 WinSetPointer(HWND_DESKTOP, hptrSnap);
                 SnapWindow(LONGFROMMP(mp1));
                 WinSetCapture(HWND_DESKTOP, (HWND)NULL);
                 fSnapWnd = FALSE;
             } else if (fSnapRgn) {
                 wptTemp.x = SHORT1FROMMP(mp1);
                 wptTemp.y = SHORT2FROMMP(mp1);
                 WinMapWindowPoints(hwndSnap, (HWND)HWND_DESKTOP,
                         (PPOINTL)&wptTemp, 1);
                 wrcRgn.yTop = wrcRgn.yBottom = wptTemp.y;
                 wrcRgn.xRight = wrcRgn.xLeft = wptTemp.x;
                 hpsScr = WinGetScreenPS(HWND_DESKTOP);
                 DrawRgn(hpsScr);
                 WinReleasePS(hpsScr);
                 fSelect = TRUE;
                 break;
             } else {
                 return(WinDefWindowProc(hwnd, message, mp1, mp2));
             }
             break;

         case WM_MOUSEMOVE:
             if (fSelect) {
                 hpsScr = WinGetScreenPS(HWND_DESKTOP);
                 DrawRgn(hpsScr);
                 wptTemp.x = LOUSHORT(mp1);
                 wptTemp.y = HIUSHORT(mp1);
                 WinMapWindowPoints(hwndSnap, (HWND)HWND_DESKTOP,
                         (PPOINTL)&wptTemp, 1);
                 wrcRgn.yTop = wptTemp.y;
                 wrcRgn.xRight = wptTemp.x;
                 DrawRgn(hpsScr);
                 WinReleasePS(hpsScr);
                 break;
             }
             if (fSnapWnd || fSnapRgn) {
                 break;
             } else {
                 return(WinDefWindowProc(hwnd, message, mp1, mp2));
             }
             break;

         case WM_BUTTON1UP:
             if (fSelect) {
                 WinSetCapture(HWND_DESKTOP, (HWND)NULL);
                 hpsScr = WinGetScreenPS(HWND_DESKTOP);
                 DrawRgn(hpsScr);
                 SnapRegion(hpsScr);
                 WinReleasePS(hpsScr);
                 fSnapRgn = FALSE;
                 fSelect = FALSE;
             }
             break;

         case WM_DRAWCLIPBOARD:
             WinInvalidateRect(hwnd, (PRECTL)NULL, TRUE);
             break;

         case WM_PAINT:
             hps = WinBeginPaint(hwnd, NULL, &wrcUpdate);
             WinFillRect(hps, &wrcUpdate, SYSCLR_WINDOW);
             SnapPaint(hps);
             WinEndPaint(hps);
             break;

         case WM_INITMENU:
             hwndMenu = WinWindowFromID(hwndSnapFrame, FID_MENU);
             WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDM_REGION, TRU
                 MPFROM2SHORT(MIA_CHECKED, wSnapMode == IDM_REGION ? MIA_CHECK
             WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDM_WINDOW, TRU
                 MPFROM2SHORT(MIA_CHECKED, wSnapMode == IDM_WINDOW ? MIA_CHECK
             WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDM_NCAREA, TRU
                 MPFROM2SHORT(MIA_CHECKED, fNCArea ? MIA_CHECKED : 0));
             WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDM_HIDE, TRUE)
                 MPFROM2SHORT(MIA_CHECKED, fHide ? MIA_CHECKED : 0));
             WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(IDM_NCAREA, TRU
                 MPFROM2SHORT(MIA_DISABLED, wSnapMode == IDM_WINDOW ? 0 : MIA_
             break;

         case WM_COMMAND:
             switch (SHORT1FROMMP(mp1)) {
                 case IDM_SAVE:
                     SaveBitmap();
                     break;

                 case IDM_SNAP:
                     WinSetCapture(HWND_DESKTOP, hwnd);
                     if (fHide)
                         WinShowWindow(hwndSnapFrame, FALSE);
                     if (wSnapMode == IDM_WINDOW) {
                         fSnapWnd = TRUE;
                         WinSetPointer(HWND_DESKTOP, hptrHand);
                     } else {
                         fSnapRgn = TRUE;
                         WinSetPointer(HWND_DESKTOP, hptrSelect);
                     }
                     break;

                 case IDM_WINDOW:
                 case IDM_REGION:
                     wSnapMode = SHORT1FROMMP(mp1);
                     break;

                 case IDM_NCAREA:
                     fNCArea = !fNCArea;
                     break;

                 case IDM_HIDE:
                     fHide = !fHide;
                     break;

                 case IDM_ABOUT:
                     WinDlgBox(HWND_DESKTOP, hwnd,
                         (PFNWP)AboutWndProc, (HMODULE) NULL, IDD_INFO, (PCH)N
                     break;
             }
             break;

         default:
             return(WinDefWindowProc(hwnd, message, mp1, mp2));
     }

     return(0L);

 } /* end snapwndproc */


 void SnapPaint(hps)
 HPS hps;
 {
     HBITMAP hbm;
     POINTL   pt;
     BITMAPINFOHEADER bminfo;
     RECTL rc;

     WinOpenClipbrd(habSnap);
     WinQueryWindowRect(hwndSnap, &rc);

     if (hbm = (HBITMAP) WinQueryClipbrdData(habSnap, CF_BITMAP)) {
         GpiQueryBitmapParameters(hbm, &bminfo);
         pt.x = 0;
         pt.y = rc.yTop - bminfo.cy;
         WinDrawBitmap(hps, hbm, (PRECTL)NULL, (PPOINTL)&pt,
                 0L, 0L, DBM_NORMAL | DBM_IMAGEATTRS);
     }

     WinCloseClipbrd(habSnap);

 } /* end SnapPaint */


 void SnapWindow(loc)
 ULONG loc;
 {
     BITMAPINFOHEADER bminfo;
     POINTL  pt;
     HWND hwnd;
     HWND hwndT;
     RECTL rc, rcTmp;
     HPS  hpsWnd, hpsMem;
     HDC  hdc;
     HBITMAP hbm, hbmOld;
     int  cx, cy;
     POINTL rgpt[3];
     SIZEL size;

     pt.y = HIUSHORT(loc);
     pt.x = LOUSHORT(loc);

     WinMapWindowPoints(hwndSnap, HWND_DESKTOP, (PPOINTL)&pt, 1);
     if ((hwnd = WinWindowFromPoint(HWND_DESKTOP, (PPOINTL)&pt, FALSE, FALSE))
         return;
     /* get size of target window. clip to screen. */
     if (fNCArea)  /* snap only the client area if it exists */
         if ((hwndT = WinWindowFromID(hwnd, FID_CLIENT)) != NULL)
             hwnd = hwndT;
     WinQueryWindowRect(hwnd, &rcTmp);
     /* bound window rectangle to screen. */
     WinMapWindowPoints(hwnd, HWND_DESKTOP, (PPOINTL)&rcTmp.xLeft, 2);
     WinIntersectRect(habSnap, &rc, &rcTmp, &rcScreen);
     WinMapWindowPoints(HWND_DESKTOP, hwnd, (PPOINTL)&rc.xLeft, 2);
     cx = (USHORT)(rc.xRight - rc.xLeft);
     cy = (USHORT)(rc.yTop - rc.yBottom);

     /* get window PS */
     hpsWnd = WinGetPS(hwnd);

     /* Create a memory DC */
     hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);

     /* create a memory PS */
     size.cx = cx;
     size.cy = cy;
     hpsMem = GpiCreatePS( habSnap, hdc, &size,
             PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC );

     /* Create a bitmap */
     bminfo.cbFix = 12;
     bminfo.cx = cx;
     bminfo.cy = cy;
     bminfo.cPlanes = 1;
     bminfo.cBitCount = 4;
     if (!(hbm = GpiCreateBitmap(hpsMem, (PBITMAPINFOHEADER)&bminfo, 0L, 0, 0)
         WinMessageBox((HWND)HWND_DESKTOP, hwndSnap,
             (PCH)"Insufficient memory to create the bitmap.", (PCH)NULL,
             0, MB_OK);
     else {
         /* put the bitmap into the memory PS */
         hbmOld = GpiSetBitmap(hpsMem, hbm);

         /* copy the window to the memory PS */
         rgpt[0].x = 0;
         rgpt[0].y = 0;
         rgpt[1].x = cx;
         rgpt[1].y = cy;
         rgpt[2].x = rc.xLeft;
         rgpt[2].y = rc.yBottom;
         GpiBitBlt(hpsMem, hpsWnd, 3L, (PPOINTL)&rgpt[0], ROP_SRCCOPY, 0L);

         /* free the bitmap */
         GpiSetBitmap(hpsMem, hbmOld);

         /* store the bitmap */
         WinOpenClipbrd(habSnap);
         WinEmptyClipbrd(habSnap);
         WinSetClipbrdData(habSnap, (ULONG)hbm, CF_BITMAP, CFI_HANDLE);
         WinCloseClipbrd(habSnap);
     }

     /* destroy the memory DC */
     GpiAssociate( hpsMem, NULL );
     DevCloseDC(hdc);

     /* get rid of the PSs */
     GpiDestroyPS(hpsMem);
     WinReleasePS(hpsWnd);

     if (fHide)
         WinShowWindow(hwndSnapFrame, TRUE);

 } /* end snapwindow */


 void SnapRegion(hpsScr)
 HPS hpsScr;
 {
     HDC hdc;
     HBITMAP hbm, hbmOld;
     BITMAPINFOHEADER bminfo;
     RECTL rcTmp;
     int cx, cy;
     POINTL rgpt[3];
     HPS hpsMem;
     SIZEL size;

     SortRect((PRECTL)&wrcRgn, (PRECTL)&rcTmp);

     cx = (USHORT)(rcTmp.xRight - rcTmp.xLeft);
     cy = (USHORT)(rcTmp.yTop - rcTmp.yBottom);

     /* Create a memory DC */
     hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);

     /* create a memory PS */
     size.cx = cx;
     size.cy = cy;
     hpsMem = GpiCreatePS( habSnap, hdc, &size,
             PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC );

     /* Create a bitmap */
     bminfo.cbFix = 12;
     bminfo.cx = cx;
     bminfo.cy = cy;
     bminfo.cPlanes = 1;
     bminfo.cBitCount = 4;
     if (!(hbm = GpiCreateBitmap(hpsMem, (PBITMAPINFOHEADER)&bminfo, 0L, 0, 0)
         WinMessageBox((HWND)HWND_DESKTOP, hwndSnap,
             (PCH)"Insufficient memory to create the bitmap.", (PCH)NULL,
             0, MB_OK);
     } else {
         /* put the bitmap into the memory PS */
         hbmOld = GpiSetBitmap(hpsMem, hbm);

         /* copy the window to the memory PS */
         rgpt[0].x = 0;
         rgpt[0].y = 0;
         rgpt[1].x = cx;
         rgpt[1].y = cy;
         rgpt[2].x = rcTmp.xLeft;
         rgpt[2].y = rcTmp.yBottom;
         GpiBitBlt(hpsMem, hpsScr, 3L, (PPOINTL)&rgpt[0], ROP_SRCCOPY, 0L);

         /* free the bitmap */
         GpiSetBitmap(hpsMem, hbmOld);

         /* store the bitmap */
         WinOpenClipbrd(habSnap);
         WinEmptyClipbrd(habSnap);
         WinSetClipbrdData(habSnap, (ULONG)hbm, CF_BITMAP, CFI_HANDLE);
         WinCloseClipbrd(habSnap);
     }
     /* destroy the memory DC */
     GpiAssociate( hpsMem, NULL );
     DevCloseDC(hdc);

     /* get rid of the PS */
     GpiDestroyPS(hpsMem);

     if (fHide)
         WinShowWindow(hwndSnapFrame, TRUE);

 } /* end snapregion */

 void DrawRgn(hps)
 HPS hps;
 {
     RECTL rc;

     SortRect((PRECTL)&wrcRgn, (PRECTL)&rc);

     WinDrawBorder(hps, (PRECTL)&rc, 1, 1, SYSCLR_WINDOW, SYSCLR_WINDOW,
             DB_DESTINVERT | DB_STANDARD);
 }

 void SortRect(pwrcIn, pwrcSorted)
 PRECTL pwrcIn, pwrcSorted;
 {
     if (pwrcIn->yTop > pwrcIn->yBottom) {
         pwrcSorted->yTop = pwrcIn->yTop;
         pwrcSorted->yBottom = pwrcIn->yBottom;
     } else {
         pwrcSorted->yTop = pwrcIn->yBottom;
         pwrcSorted->yBottom = pwrcIn->yTop;
     }
     if (pwrcIn->xRight > pwrcIn-> xLeft) {
         pwrcSorted->xRight = pwrcIn->xRight;
         pwrcSorted->xLeft = pwrcIn->xLeft;
     } else {
         pwrcSorted->xRight = pwrcIn->xLeft;
         pwrcSorted->xLeft = pwrcIn->xRight;
     }
 }

 MRESULT EXPENTRY AboutWndProc(hwnd, message, mp1, mp2)
 HWND    hwnd;
 USHORT  message;
 MPARAM   mp1;
 MPARAM   mp2;
 {
     switch (message) {
         case WM_COMMAND:
             WinDismissDlg(hwnd, TRUE);
             break;
         default:
             return(WinDefDlgProc(hwnd, message, mp1, mp2));
             break;
     }
     return 0L;

 } /* end aboutwndproc */

 void SaveBitmap(void)
 {
     HFILE hFile;
     HBITMAP hbm;
     HPOINTER hptr, hptrWait;

     WinOpenClipbrd(habSnap);
     if ((hbm = (HBITMAP) WinQueryClipbrdData(habSnap, CF_BITMAP)) == NULL) {
         WinCloseClipbrd(habSnap);
         return;
     }

     if (hFile = OpenSaveFile(hwndSnap)) {
         hptr = WinQueryPointer(HWND_DESKTOP);
         hptrWait = WinQuerySysPointer(HWND_DESKTOP, SPTR_WAIT, TRUE);
         WinSetPointer(HWND_DESKTOP, hptrWait);
         SaveBitmap2(hFile, hbm);
         WinSetPointer(HWND_DESKTOP, hptr);
         WinDestroyPointer(hptrWait);
     } else
         WinAlarm(HWND_DESKTOP, WA_ERROR);

     WinCloseClipbrd(habSnap);
 }

 /***************************************************************************\
 * hFile is a handle to an open file.  This is closed on exit.
 \***************************************************************************/
 void SaveBitmap2(hFile, hbm)
 HFILE hFile;
 HBITMAP hbm;
 {
     /*
      * Currently, this puts stuff out in Win386 paint format.
      */
     typedef struct _WIN386PAINT {
         USHORT key1;
         USHORT key2;
         USHORT dxFile;
         USHORT dyFile;
         USHORT ScrAspectX;
         USHORT ScrAspectY;
         USHORT PrnAspectX;
         USHORT PrnAspectY;
         USHORT dxPrinter;
         USHORT dyPrinter;
         USHORT AspCorX;
         USHORT AspCorY;
         USHORT wCheck;
         USHORT res1;
         USHORT res2;
         USHORT res3;
     } FHDR;
     FHDR hdr;
     USHORT i;
     USHORT cBytesWritten;
     USHORT *pIndex;
     USHORT *pIndexT;
     NPBYTE pScanLine, pBits, pBitsT;
     USHORT *phdr;
     USHORT cbIndexTable, cbScanLine, cbScanLineExp, cbBmpLine;
     HDC hdc;
     HBITMAP hbmOld;
     BITMAPINFOHEADER bminfo;
     HPS hpsMem;
     SIZEL size;

     /*
      * write header
      */
     GpiQueryBitmapParameters(hbm, &bminfo);
     hdr.key1 = 0x694C;
     hdr.key2 = 0x536E;
     hdr.dxFile = bminfo.cx;
     hdr.dyFile = bminfo.cy;
     hdr.ScrAspectX = 26;
     hdr.ScrAspectY = 30;
     hdr.PrnAspectX = 0x12c;
     hdr.PrnAspectY = 0x12c;
     hdr.dxPrinter = 0x8df;
     hdr.dyPrinter = 0xce1;
     hdr.AspCorX = 0;
     hdr.AspCorY = 0;

     phdr = (USHORT *)&hdr;
     hdr.wCheck = 0;
     for (i=0; i < 12; i++)
         hdr.wCheck ^= *phdr++;

     hdr.res1 = 0;
     hdr.res2 = 0;
     hdr.res3 = 0;

     DosWrite(hFile, (PSZ)&hdr, sizeof(FHDR), (PUSHORT)&cBytesWritten);

     /* calculate sizes */
     cbIndexTable = sizeof(unsigned int) * bminfo.cy;
     cbScanLine = (bminfo.cx + 7) >> 3;
     cbScanLineExp = cbScanLine + ((cbScanLine + 0xff) >> 8);
     cbBmpLine = (cbScanLine + 3) & 0xfffc;

     /*
      * Write index table - (no compression)
      */
     /* allocate pIndex */
     if ((pIndex = (USHORT *)WinAllocMem(hHeap, cbIndexTable)) == NULL) {
         WinFreeMem(hHeap, (NPBYTE)pIndex, cbIndexTable);
         goto Exit;
     }
     pIndexT = pIndex;
     for (i=0; i < bminfo.cy; i++)
         *pIndexT++ = cbScanLineExp;

     DosWrite(hFile, (PSZ)pIndex, cbIndexTable, &cBytesWritten);

     /* free pIndex */
     WinFreeMem(hHeap, (NPBYTE)pIndex, cbIndexTable);

     /*
      * Write out each scan line
      */
     /* allocate pScanLine */
     if ((pScanLine = WinAllocMem(hHeap, cbScanLineExp)) == NULL) {
         WinFreeMem(hHeap, (NPBYTE)pScanLine, cbScanLineExp);
         goto Exit;
     }
     /* allocate pBits */
     if ((pBits = WinAllocMem(hHeap, cbBmpLine * bminfo.cy)) == NULL) {
         WinFreeMem(hHeap, pBits, cbBmpLine * bminfo.cy);
         goto Exit;
     }

     /* specify the bitmap format we want */
     bminfo.cPlanes = 1;
     bminfo.cBitCount = 1;

     /* Create memory DC */
     hdc = CreateDC((PSZ)"MEMORY", (HDC)NULL);

     /* create a memory PS */
     size.cx = bminfo.cx;
     size.cy = bminfo.cy;
     hpsMem = GpiCreatePS( habSnap, hdc, &size,
             PU_ARBITRARY | GPIT_NORMAL | GPIA_ASSOC);

     hbmOld = GpiSetBitmap(hpsMem, hbm);
     GpiQueryBitmapBits(hpsMem, (LONG)0, (LONG)bminfo.cy, (PBYTE)pBits,
             (PBITMAPINFO)&bminfo);

     pBitsT = pBits + cbBmpLine * bminfo.cy;
     for (i = 0; i < bminfo.cy; i++) {
         pBitsT -= cbBmpLine;
         Copy(pBitsT, pScanLine, cbScanLine);
         convert(pScanLine, cbScanLine, cbScanLineExp);
         DosWrite(hFile, (PSZ)pScanLine, cbScanLineExp, &cBytesWritten);
     }

     /* free pBits and pScanLine */
     WinFreeMem(hHeap, pBits, cbScanLine * bminfo.cy);
     WinFreeMem(hHeap, pScanLine, cbScanLineExp);

     GpiAssociate(hpsMem, NULL);
     DevCloseDC(hdc);
     GpiDestroyPS(hpsMem);

 Exit:
     DosClose(hFile);
 }


 /***************************************************************************\
 * Insert apropriate repeat count bytes into the scanline given.
 \***************************************************************************/
 void convert(pbuf, cch, cch2)
 NPBYTE pbuf;
 USHORT cch; /* exact length of buffer BEFORE count bytes have been added */
 USHORT cch2; /* AFTER conversion size */
 {
     NPBYTE pWrite, pRead;

     pWrite = pbuf + cch2 - 1;
     pRead = pbuf + cch - 1;
     while (pWrite > pbuf) {
         *pWrite-- = *pRead--;
         if (((pWrite - pbuf) & 0x00ff) == 0) {
             *pWrite-- = (BYTE)((pbuf + cch2 - pWrite) < 0x100 ?
                     (pbuf + cch2 - pWrite) : 0xff);
         }
     }
 }

 void Copy(src, dest, cb)
 NPBYTE src;
 NPBYTE dest;
 USHORT cb;
 {
     USHORT i;

     for (i = 0; i < cb; i++)
         *dest++ = *src++;
 }

 HFILE OpenSaveFile(hwnd)
 HWND hwnd;
 {
     HFILE   hFile= 0L;
     DLGP    dlgp;
     char    fName[32];
     USHORT  action;

     dlgp.cch = 32;
     dlgp.psz = fName;
     if (WinDlgBox(HWND_DESKTOP, hwnd, (PFNWP)SaveFileDlgProc, (HMODULE) NULL,
             IDD_SAVEFILE, (PVOID)&dlgp) != 0) {
         DosOpen(fName, &hFile, &action, 0L, 0, 0x0011, 0x0011, 0L);
     }
     return(hFile);
 }

 MRESULT EXPENTRY SaveFileDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     DLGP FAR *pdlgp;
     SHORT cch;

     switch (msg) {
     case WM_INITDLG:
         /* squirl away the dlgp pointer in a reentrant fashion */
         WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
         break;

     case WM_CHAR:
         if ( (SHORT1FROMMP(mp1) & KC_VIRTUALKEY) &&
                 ((SHORT2FROMMP(mp2) == VK_NEWLINE) ||
                  (SHORT2FROMMP(mp2) == VK_ENTER)) ) {
             if (pdlgp = (DLGP FAR *)WinQueryWindowULong(hwnd, QWL_USER)) {
                 cch = WinQueryWindowText(WinWindowFromID(hwnd, ID_FILEENTRY),
                         pdlgp->cch, pdlgp->psz);
                 WinDismissDlg(hwnd, cch);
             } else {
                 WinDismissDlg(hwnd, 0);
             }
             return(0);
         }
         break;
     }

     return(WinDefDlgProc(hwnd, msg, mp1, mp2));
 }


 SORT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SORT\SORT.C

 /*************************************************************

  This program demonstrates the use of many threads by doing
  LISTCNT simultaneous sorts.  Each sorting algorithm runs
  from a separate thread, the routine which updates the display
  is run from another thread, and the main thread is used to
  handle the main window's messages.  The display thread is
  started when the program begins and is not terminated (allows
  default cleanup to terminate thread) as the display routine
  is used throughout the program's life.
  This code was written to allow easy modification.  To change
  the number of simultaneous sorts, change the LISTCNT #define
  and modify the global arrays which control the screen
  drawing color, the sort each thread is to use, and the sort name.
  Note:  This program was not intended to infer the relative
         speeds of the various sorting algorithms.

  This program was written by Jeff Johnson, 8/89.

  Procedures in this file:
    main()             Sets up the PM environment and calls
                       the main dialog procedure ClientWndProc
    ClientWndProc()    Handles the main window messages
    CalcThread()       Generic stub that sets up, executes, and
                       terminates each sort in an aux. thread
    DispThread()       Updates the display during the sorts
    BubbleSort()       Implements a bubble sort
    InsertionSort()    Implements an insertion sort
    BatcherSort()      Stub that calls recursive Batcher sort
    BatcherSortR()     Implements a Batcher sort
    QuickSort()        Stub that calls recursive Quick sort
    QuickSortR()       Implements a Quick sort
    EnableMenuItem()   Activates/deactivates a menu choice
    EntryFldDlgProc()  Handles the set number of disks dialog box
    RandomizeData()    Randomizes the data arrays for the sorts

 **************************************************************/

 #include "sort.h"

 /********************* GLOBALS *******************************/

 CHAR szClientClass[] = "Sort";
 USHORT Data[LISTCNT][NELEMS];     /* Array that contains the data to sort  */

 /* Global data that defines each of the sort routines */
 ULONG  ulColors[LISTCNT] =   { CLR_RED,        /* Color of each of the     */
                                CLR_BLUE,       /* LISTCNT data lists.      */
                                CLR_DARKGREEN,
                                CLR_YELLOW };
 VOID((*pExecSub[LISTCNT])())=  { BubbleSort,     /* List of sorts to be
                                BatcherSort,    /* executed for each of the */
                                QuickSort,      /* LISTCNT threads.         */
                                InsertionSort };
 CHAR *szSubNames[LISTCNT] =  { "Bubble Sort",  /* Ascii names of sorts     */
                                "Batcher Sort",
                                "Quick Sort",
                                "Insertion Sort" };

 /*************************************************************/


 /*
  * Function name: main()
  *
  * Parameters:  NONE
  *
  * Returns: Always returns 0
  *
  * Purpose: Sets up the PM environment, calls the main window proc,
             handles the window's messages then cleans up and exits.
  *
  * Usage/Warnings:
  *
  * Calls: ClientWndProc() (thru PM)
  */

 int main()
 {
    HAB          hab;
    HMQ          hmq;
    HWND         hwndFrame, hwndClient;
    QMSG         qmsg;

    ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU | FCF_MINMAX    |
                         FCF_SHELLPOSITION | FCF_MENU    | FCF_TASKLIST  |
                         FCF_SIZEBORDER    | FCF_ICON;

    /* These PM calls should be error checked */
    hab = WinInitialize(0);
    hmq = WinCreateMsgQueue(hab, 0);

    if(!WinRegisterClass(hab, szClientClass,ClientWndProc,0L,0))
    {
       WinAlarm(HWND_DESKTOP, WA_ERROR);        /* Register failed */
       DosExit(EXIT_PROCESS,1);
    }

    hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE,
                                   &flFrameFlags, szClientClass, NULL,
                                   0L, (HMODULE) NULL, ID_MAINMENU, &hwndClien
    if(!hwndFrame)
    {
       WinAlarm(HWND_DESKTOP, WA_ERROR); /* Window create failed */
       DosExit(EXIT_PROCESS,1);
    }

    while(WinGetMsg(hab,&qmsg,NULL,0,0))        /* Message loop */
       WinDispatchMsg(hab,&qmsg);

    WinDestroyWindow(hwndFrame);                /* Clean up     */
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);
    return 0;
 }


 /*
  * Function name: ClientWndProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              No user data is expected in the WM_CREATE.
  *
  * Returns: Returns with WM_QUIT message
  *
  * Purpose: Handles all the messages associated with the main window
  *          and calls the appropriate handling procedures.
  *
  * Usage/Warnings: Called only by main()
  *
  * Calls: RandomizeData(), DispThread() (thru thread),
           CalcThread() (thru thread)
  */

 MRESULT EXPENTRY ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
    HPS    hps;                           /* Handle for painting            */
    RECTL  rcl;                           /* Rectangle struct for painting  */
    POINTL ptl;                           /* Point struct for painting      */
    static CALCPARAM cp[LISTCNT];         /* Struct passed to sort threads  */
    static TID    tidCalc[LISTCNT];       /* Sort threads IDs               */
    static VOID   *pThreadStack[LISTCNT]; /* Pointer to sort threads stacks */
    static CALCPARAM cpDisp;              /* Struct passed to disp thread   */
    static TID    tidDisplay;             /* Secondary display thread ID    */
    static VOID   *pDisplayStack;         /* Ptr to secondary display stack */
    static USHORT usWindowYCoord=1;       /* Holds current client window ht */
    static BYTE   cThreadCnt = 0;         /* Count of sort threads running  */
    static USHORT cSetSize = NELEMS;      /* User sort set size             */
    USHORT cnt,cnt2,usID;                 /* Utility counters               */
    ULONG  ulBlip;                        /* Holds the marker pos for Paint */

    switch(msg)
    {
       case WM_CREATE:
          RandomizeData(cSetSize);        /* Initially randomize data set   */

          /* Start display thread */
          if((pDisplayStack = malloc(STACKSIZE)) == NULL)
          {
             WinAlarm(HWND_DESKTOP, WA_ERROR);      /* Can't allocate stack */
             DosExit(EXIT_PROCESS,1);
          }

          cpDisp.hwnd        = hwnd;
          cpDisp.Array       = &usWindowYCoord;
          cpDisp.pcThreadCnt = &cThreadCnt;
          cpDisp.pcSetSize   = &cSetSize;

          if((tidDisplay = _beginthread(DispThread,pDisplayStack,
                                          STACKSIZE, &cpDisp))  == -1)
          {
             free(pDisplayStack);          /* Couldn't start display thread */
             WinAlarm(HWND_DESKTOP, WA_ERROR);
             DosExit(EXIT_PROCESS,1);
          }
          /* Up the display priority an arbitrary amount to cause it to
             update more rapidly and to give it a higher priority than
             the sort threads.*/
          DosSetPrty(PRTYS_THREAD,
                     PRTYC_REGULAR,5,tidDisplay);
          return 0L;

       case WM_SIZE:
          WinInvalidateRect(hwnd,NULL,FALSE);     /* Force a redraw */
          return WinDefWindowProc(hwnd, msg, mp1, mp2);

       case WM_PAINT:
          hps = WinBeginPaint(hwnd,NULL,NULL);   /* Get paint handle      */
          WinQueryWindowRect(hwnd,&rcl);
          usWindowYCoord = (USHORT) rcl.yTop;

          if((XOFFSET < (USHORT) (rcl.xRight-rcl.xLeft)) && /* Draw if screen
             (YOFFSET < (USHORT) (rcl.yTop-rcl.yBottom)))   /* is big enough
          {
             DrawRect(rcl.xLeft,rcl.yBottom,         /* White out the screen
                      rcl.xRight,rcl.yTop,CLR_WHITE);

             DrawLine(XOFFSET, YOFFSET,              /* Draw baseline
                      rcl.xRight,YOFFSET,CLR_BLACK);

             DrawLine(XOFFSET, YOFFSET,              /* Draw vertical line
                      XOFFSET,rcl.yTop,CLR_BLACK);

             for(cnt=0;cnt<LISTCNT;cnt++)            /* Draw data points
             {
                for(cnt2=0;cnt2<cSetSize;cnt2++)
                {
                   ulBlip = (rcl.yTop-YOFFSET-5) * Data[cnt][cnt2] / RAND_MAX
                            + YOFFSET+1;

                   Draw2Pel(XOFFSET+1 + cnt2, ulBlip, ulColors[cnt]);
                }
             }
             for(cnt=0;cnt<COLUMNCNT;cnt++)          /* Do bottom legend
             {
                for(cnt2=1;cnt2<=ROWCNT;cnt2++)
                {
                   usID = cnt*ROWCNT + cnt2-1;
                   if(usID >= LISTCNT)
                      break;

                   ptl.x = XOFFSET + cnt*COLUMNOFFSET;
                   ptl.y = YOFFSET - cnt2*ROWOFFSET;

                   GpiSetColor(hps,ulColors[usID]);
                   GpiCharStringAt(hps,&ptl,(LONG) strlen(szSubNames[usID]),
                                   szSubNames[usID]);
                }
             }
          }

          WinEndPaint(hps);
          return 0L;

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case IDM_START:

                /* Try to get stack space */
                for(cnt=0;cnt<LISTCNT;cnt++)
                {
                   if((pThreadStack[cnt] = malloc(STACKSIZE)) == NULL)
                   {
                      WinAlarm(HWND_DESKTOP, WA_ERROR); /* Can't get memory */
                      return 0L;
                   }
                   cp[cnt].hwnd          = hwnd;  /* Set the static struct  */
                   cp[cnt].fContinueCalc = TRUE;
                   cp[cnt].pFunc         = pExecSub[cnt];
                   cp[cnt].usID          = cnt;
                   cp[cnt].Array         = Data[cnt];
                   cp[cnt].cArray        = cSetSize;

                   /* Try to start the thread */
                   if((tidCalc[cnt] = _beginthread(CalcThread,pThreadStack[cnt
                                                   STACKSIZE, &cp[cnt]))  == -
                   {
                      free(pThreadStack[cnt]);    /* Thread wouldn't start  */
                      WinAlarm(HWND_DESKTOP, WA_ERROR);
                      return 0L;
                   }
                   if(cThreadCnt++ == 0)  /* When the first thread starts */
                   {
                      /* Disable Start, Set, and Randomize, enable Stop */
                      EnableMenuItem(hwnd,IDM_START,FALSE);
                      EnableMenuItem(hwnd,IDM_SET,FALSE);
                      EnableMenuItem(hwnd,IDM_RANDOM,FALSE);
                      EnableMenuItem(hwnd,IDM_STOP,TRUE);
                   }
                }
                return 0L;

             case IDM_STOP:
                for(cnt=0;cnt<LISTCNT;cnt++)
                   cp[cnt].fContinueCalc = FALSE;  /* Notify thread to quit  *
                return 0L;

             case IDM_RANDOM:
                RandomizeData(cSetSize);                /* Randomize data */
                WinInvalidateRect(hwnd,NULL,FALSE);     /* Force a redraw */
                return 0L;

             case IDM_SET:
                if(WinDlgBox(HWND_DESKTOP, hwnd, /* Pop up the query/set box *
                             EntryFldDlgProc,(HMODULE) NULL,ID_SETCOUNT,
                             (VOID FAR *) &cSetSize))
                {
                   WinInvalidateRect(hwnd,NULL,FALSE);     /* Force a redraw *
                }
                return 0L;

             default:
                return WinDefWindowProc(hwnd, msg, mp1, mp2);
          }

       case UM_CALC_DONE:
          usID = (USHORT) SHORT1FROMMP(mp1); /* Get ID of quit thread     */

          if(--cThreadCnt == 0)              /* If all quit, enable menus */
          {
             EnableMenuItem(hwnd,IDM_START,TRUE);
             EnableMenuItem(hwnd,IDM_SET,TRUE);
             EnableMenuItem(hwnd,IDM_RANDOM,TRUE);
             EnableMenuItem(hwnd,IDM_STOP,FALSE);
          }

          free(pThreadStack[usID]);          /* Free thread's stack space */
          return 0L;

       default:
          return WinDefWindowProc(hwnd, msg, mp1, mp2);
    }
 }


 /*
  * Function name: CalcThread()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle, the
  *              continue flag which is initially set to TRUE, the ID
  *              which is the
  *
  * Returns: VOID
  *
  * Purpose: This generic stub calls the passed "sort" function with a pointer
  *          to the data and an item count, then when the sort terminates, it
  *          cleans up by Posting a done message, then terminating the thread.
  *
  * Usage/Warnings: No DosExitCritSec() is called since _endthread()
  *                 clears the critical section when the thread is
  *                 terminated.
  *
  * Calls:  Whatever function is passed in pcp->pFunc.  (As initially
  *         configured, it calls:  BubbleSort(), InsertionSort(),
  *         BatcherSort(), and QuickSort()
  */

 VOID _cdecl FAR CalcThread(PCALCPARAM pcp)
 {
    (*pcp->pFunc)(pcp);                      /* Execute recurs routine */

    DosEnterCritSec(); /* Set Crit so the UM_CALC_DONE isn't processed */
                       /* until this thread has completely terminated  */
    WinPostMsg(pcp->hwnd,UM_CALC_DONE,
               MPFROMSHORT(pcp->usID),NULL);              /* Post done */

    _endthread();                /* Terminate thread and exit crit sec */
 }


 /*
  * Function name: DispThread()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle, the
  *              count of active threads, the ->Array element points to
  *              the current height of the window which can be dynamically
  *              resized by the user, the pcSetSize points to the current
  *              set size which can be changed by the user only when the
  *              sort is stopped.
  *              None of the other pcp members are used.
  *
  * Returns: VOID
  *
  * Purpose: This routine is run as a secondary thread to update the screen
  *          when the sort threads are running.  It works by making passes
  *          through the data lists and redrawing them.  A DosSleep(0) is
  *          called at the end of each pass to help sychronize things a bit.
  *
  * Usage/Warnings: Note that this thread is started when the program is
  *                 initialized, and is never terminated.  Note that
  *                 fDoUpdate is used in such a way that the screen is
  *                 ALWAYS updated once after all threads have terminated.
  *                 This avoids the possibility of the display not being
  *                 fully accurate when the sorts terminate.
  *
  * Calls:
  */

 VOID _cdecl FAR DispThread(PCALCPARAM pcp)
 {
    HAB hab;              /* Anchor block, used just for WinInitialize() */
    USHORT cnt,cnt2;      /* Utility counters                            */
    HPS hps;              /* Presentation space handle                   */
    POINTL ptl;           /* Used for various drawing macros             */
    ULONG  ulBlip;        /* Holds the Y offset for a given data point   */
    BYTE   fDoUpdate = 0; /* Used to ensure screen is fully updated      */
                          /* after all sort threads terminate            */
    USHORT *pusYCoord = pcp->Array; /* Points to location that always    */
                                    /* contains current client wind hgt  */

    hab = WinInitialize(0);    /* Called to increase Ring 2 stack size   */
    hps = WinGetPS(pcp->hwnd);

    while(TRUE)
    {
       if(*pcp->pcThreadCnt != 0)          /* Set update flag if at least 1 */
          fDoUpdate = (BYTE) (*pcp->pcThreadCnt+1); /* sort thread is still ru

       while(!fDoUpdate && !(*pcp->pcThreadCnt));  /* Only update when a    */
                                                   /* sort is running       */
       for(cnt=0;cnt<*pcp->pcSetSize;cnt++)        /* Update data set       */
       {
          DrawLine(XOFFSET+1 + cnt,YOFFSET+1,      /* Erase vertical column */
                   XOFFSET+1 + cnt,*pusYCoord, CLR_WHITE);

          for(cnt2=0;cnt2<LISTCNT;cnt2++)          /* Draw each point       */
          {
             ulBlip = (ULONG) (*pusYCoord-YOFFSET-5)*Data[cnt2][cnt] / RAND_MA
                       + YOFFSET+1;
             Draw2Pel(XOFFSET+1 + cnt, ulBlip, ulColors[cnt2]);
          }
       }
       fDoUpdate--;  /* Decrement update flag */
    }

    /* Note that these 3 lines NEVER get executed, but should the program be
       modified to end the thread, this is the appropriate termination code. *
    WinReleasePS(hps);
    WinTerminate(hab);
    _endthread();
 }


 /*
  * Function name: BubbleSort()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle, a
  *              pointer to the data array, and a count of the items in
  *              the data array.
  *              None of the other pcp members are used.
  *
  * Returns: VOID
  *
  * Purpose:  Implements a bubble sort which is an O(n^2) algorithm.  It
  *           works by repeatedly going through the data and comparing
  *           consecutive elements and swapping them if they aren't in
  *           the correct order.  This guarantees that each pass will
  *           place at least one additional item in its appropriate
  *           place.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 VOID BubbleSort(PCALCPARAM pcp)
 {
    BOOL fModified = FALSE; /* Set whenever a swap is done, if an entire */
                            /* pass doesn't set the flag, the we're done */
    SHORT cnt,cnt2;         /* Counters used for the 2-level loops       */
    USHORT usTemp;          /* Used to hold a data item during a swap    */

    for(cnt=pcp->cArray-1;cnt>=0;cnt--) /* Set for the max no. of passes */
    {
       for(cnt2=0;cnt2<cnt;cnt2++) /* Only sort thru current cnt pass    */
       {
          if(!pcp->fContinueCalc)  /* User wishes to terminate the sort  */
             return;

          if(pcp->Array[cnt2]>pcp->Array[cnt2+1]) /* Items need to swap  */
          {
             fModified = TRUE;

             usTemp = pcp->Array[cnt2];
             pcp->Array[cnt2] = pcp->Array[cnt2+1];
             pcp->Array[cnt2+1]= usTemp;
          }
       }
       if(!fModified)          /* Nothing changed during the entire pass */
          break;
       fModified = FALSE;                       /* Reset the modify flag */
    }
 }


 /*
  * Function name: InsertionSort()
  *
  * Parameters:  pcp is a struct which contains the hwnd handle, a
  *              pointer to the data array, and a count of the items in
  *              the data array.
  *              None of the other pcp members are used.
  *
  * Returns: VOID
  *
  * Purpose:  Implements an insertion sort which is an O(n^2) algorithm.
  *           This sort works much faster and does not require that much
  *           additional code to implement.  It works by setting a sorted
  *           list to be just the first element, the working each
  *           successive item into the already sorted list.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 VOID InsertionSort(PCALCPARAM pcp)
 {
    SHORT cnt,cnt2;         /* Counters used for the 2-level loops       */
    USHORT usTemp;          /* Used to hold a data item during a swap    */

    for(cnt=1;cnt<(SHORT)pcp->cArray;cnt++)         /* Insert each item in tur
    {
       if(!pcp->fContinueCalc)      /* User wishes to terminate the sort */
          return;
       usTemp=pcp->Array[cnt];                   /* Hold value to insert */
       cnt2=cnt-1;
       while(pcp->Array[cnt2]>usTemp)    /* Move items down to make room */
       {
          pcp->Array[cnt2+1]=pcp->Array[cnt2];
          cnt2--;
          if(cnt2<0)
             break;
       }
       pcp->Array[cnt2+1] = usTemp;                   /* Insert the item */
    }
 }


 /*
  * Function name: BatcherSort()
  *
  * Parameters:  pcp is a struct which contains the continue flag,
  *              a pointer to the data array, and a count of the items in
  *              the data array.
  *              None of the other pcp members are used.
  *
  * Returns: VOID
  *
  * Purpose:  This routine is a stub that calls the recursive Batcher sort
  *           with the proper initial arguments.
  *
  * Usage/Warnings:  It passes the pcp, the size of the array to be sorted,
  *                  the offset to start sorting at(0), the number number
  *                  of elements each item is from neighboring elements (1),
  *                  and Half flag to sort the halves (1=YES).
  *
  * Calls:  BatcherSortR()
  */

 VOID BatcherSort(PCALCPARAM pcp)
 {
    BatcherSortR(pcp,pcp->cArray,0,1,1);
 }


 /*
  * Function name: BatcherSortR()
  *
  * Parameters:  pcp is a struct which contains the continue flag,
  *              a pointer to the data array.
  *              None of the other pcp members are used.
  *              usArrSize is the number of elements in the current sort set.
  *              usStart is the offset to the 1st element in the set.
  *              usSkip is the spacing between consecutive elements.
  *              fHalves sorts the 2 halves when set, otherwise skips the
  *                      1st 2 of the 4 sub-sorts.
  *
  * Returns: VOID
  *
  * Purpose:  Implements Batcher sort which is O(n lg n).  The advantage
  *           of the batcher sort is that the comparisons made do NOT
  *           depend upon the outcome of previous comparisons.  This makes
  *           it a good algorithm for parallelism.  The algorithm works as
  *           follows:  Sort the first half, sort the second half, sort the
  *           odd elements, sort the even elements, then compare swap
  *           elements 2/3, 4/5, ...
  *
  * Usage/Warnings:  There are several adaptations to the algorithm to
  *                  allow it to work with arbitrary sized data sets
  *                  (the original required a power of 2 sized data
  *                  set):  if the set size is less than 2, the routine
  *                  returns, the first "half" is always the largest
  *                  possible power of two, and the top value for the
  *                  final compare/swap is adjusted to round up in
  *                  case of an odd data set.
  *                  Another optimization is that involving the fHalves
  *                  flag.  This stems from the observation that when
  *                  the odd/even sort recurses, the first and second
  *                  halves are already sorted, thus the first 2
  *                  recursive calls are unnecessary in this case.
  *
  * Calls:  BatcherSortR() (recursively)
  */

 VOID BatcherSortR(PCALCPARAM pcp, USHORT usArrSize, USHORT usStart,
                   USHORT usSkip, BOOL fHalves)
 {
    USHORT cnt,usUpper,usTemp; /* Utility variables */

    if(!pcp->fContinueCalc) /* User wishes to terminate the sort */
       return;

    if(usArrSize<2) /* No sorting needed if <2 items in the set */
       return;

    if(usArrSize==2) /* Do simple compare/swap if there are 2 elements */
    {
       if(pcp->Array[usStart]>pcp->Array[usStart+usSkip])
       {
          usTemp = pcp->Array[usStart];
          pcp->Array[usStart] = pcp->Array[usStart+usSkip];
          pcp->Array[usStart+usSkip]= usTemp;
       }
       return;
    }

    usTemp=1;                  /* usTemp ends up holding the smallest power */
    while(usTemp < usArrSize)  /* of 2 that is at least as big as usArrSize */
       usTemp *= 2;

    if(fHalves)  /* If the sort was NOT called by the odd/even recurses */
    {
       BatcherSortR(pcp,usTemp/2,usStart,usSkip,1);    /* Sort 1st half */
       BatcherSortR(pcp,usArrSize-usTemp/2,            /* Sort 2nd half */
                    usStart+usTemp/2*usSkip,usSkip,1);
    }
    BatcherSortR(pcp,usArrSize-usArrSize/2,usStart,usSkip*2,0); /* Sort evens
    BatcherSortR(pcp,usArrSize/2,usStart+usSkip,usSkip*2,0);    /* Sort odds

    if(!pcp->fContinueCalc) /* User wishes to terminate the sort */
       return;

    usUpper=usStart+usSkip+(usArrSize-usArrSize/2-1)*2*usSkip;
    for(cnt=usStart+usSkip;cnt<usUpper;cnt+=usSkip*2) /* Do final compares */
    {
       if(pcp->Array[cnt]>pcp->Array[cnt+usSkip])
       {
          usTemp = pcp->Array[cnt];
          pcp->Array[cnt] = pcp->Array[cnt+usSkip];
          pcp->Array[cnt+usSkip]= usTemp;
       }
       if(!pcp->fContinueCalc) /* User wishes to terminate the sort */
          return;
    }
 }


 /*
  * Function name: QuickSort()
  *
  * Parameters:  pcp is a struct which contains the continue flag,
  *              a pointer to the data array, and a count of the items in
  *              the data array.
  *              None of the other pcp members are used.
  *
  * Returns: VOID
  *
  * Purpose:  This routine is a stub that calls the recursive Quick sort
  *           with the proper initial arguments.
  *
  * Usage/Warnings:  It passes the pcp (Which contains the array ptr),
  *                  the offset to start sorting at(0), and the offset to
  *                  finish sorting at (Array size-1).
  *
  * Calls:  QuickSortR()
  */

 VOID QuickSort(PCALCPARAM pcp)
 {
    QuickSortR(pcp,0,pcp->cArray-1);
 }


 /*
  * Function name: QuickSortR()
  *
  * Parameters:  pcp is a struct which contains the continue flag,
  *              a pointer to the data array.
  *              None of the other pcp members are used.
  *              sLeft is the offset of the leftmost sort element (smallest).
  *              sRight is the offset of rightmost sort element (largest).
  *
  * Returns: VOID
  *
  * Purpose:  Implements Quick sort which is O(n lg n).  Quick sort is a
  *           good all-purpose sorting algorithm and is widely used.  This
  *           implementation works by placing the first element in the list
  *           into its correct place, by moving all elements smaller to its
  *           left, and all numbers larger to its right.  The Quick sort
  *           recurses on the 2 halves on either side of the properly placed
  *           element.
  *
  * Usage/Warnings:
  *
  * Calls:  QuickSortR() (recursively)
  */

 VOID QuickSortR(PCALCPARAM pcp, SHORT sLeft, SHORT sRight)
 {
    SHORT sTempLeft  = sLeft;    /* Holds lower bound on split position      *
    SHORT sTempRight = sRight;   /* Holds upper bound on split position      *
    SHORT sSplit;                /* Used to hold offset of positioned elem   *
    USHORT usTemp;               /* Holds temp element during positioning    *

    if(sLeft>=sRight)            /* If there is <=1 element, return          *
       return;

    if(!pcp->fContinueCalc)      /* User wishes to terminate the sort        *
       return;

    /* Split list, sSplit will contain the split element */
    usTemp = pcp->Array[sTempLeft];

    while(TRUE)       /* Loop thru 2 cases until the split position is found *
    {
       while(pcp->Array[sTempRight] > usTemp && /* Skip elems on right side  *
                         sTempRight > sLeft)    /* that are > the split elem *
          sTempRight--;

       if(sTempRight<=sTempLeft)  /* Left and Right have met, split is found *
       {
          pcp->Array[sTempLeft]=usTemp;  /* Place split elem at proper place *
          sSplit = sTempLeft;
          break;
       }
       pcp->Array[sTempLeft++] =
                     pcp->Array[sTempRight];    /* Move small element to LHS *

       while(pcp->Array[sTempLeft] < usTemp &&  /* Skip elems on left side   *
                         sTempLeft < sRight)    /* that are < the split elem *
          sTempLeft++;

       if(sTempRight<=sTempLeft)  /* Left and Right have met, split is found *
       {
          pcp->Array[sTempRight]=usTemp; /* Place split elem at proper place *
          sSplit = sTempRight;
          break;
       }
       pcp->Array[sTempRight--] =
                     pcp->Array[sTempLeft];     /* Move large element to RHS *
    }

    QuickSortR(pcp,sLeft,sSplit-1);     /* Sort the 1st half of the list     *

    if(!pcp->fContinueCalc)             /* User wishes to terminate the sort *
       return;

    QuickSortR(pcp,sSplit+1,sRight);    /* Sort the 2nd half of the list     *
 }


 /*
  * Function name: EnableMenuItem()
  *
  * Parameters:  hwnd is a handle of the current window.
  *              sMenuItem is the ID of the item to Enable/Disable.
  *              fEnable will enable item if TRUE, otherwise disables it.
  *
  * Returns: VOID
  *
  * Purpose: This routine handles enabling/disabling of menu items.  This
  *          is done by getting Parent and Menu hwnd handles then sending
  *          the appropriate message.
  *
  * Usage/Warnings:
  *
  * Calls:
  */

 VOID EnableMenuItem(HWND hwnd, SHORT sMenuItem, BOOL fEnable)
 {
    HWND hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
    HWND hwndMenu   = WinWindowFromID(hwndParent, FID_MENU);

    WinSendMsg(hwndMenu, MM_SETITEMATTR,
               MPFROM2SHORT(sMenuItem, TRUE),
               MPFROM2SHORT(MIA_DISABLED, fEnable ? 0 : MIA_DISABLED));
 }


 /*
  * Function name: EntryFldDlgProc()
  *
  * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
  *              A pointer to the current set size is passed in WM_INITDLG.
  *
  * Returns: Terminates with a TRUE iff a new valid data set size is entered.
  *
  * Purpose: Handles all the messages associated with the set entry field
  *          and calls the appropriate handling procedures.  The purpose
  *          of this dialog box is to get a new data set size for the
  *          sort routines.
  *
  *
  * Usage/Warnings: If the value entered is valid, the proc will set the
  *                 variable passed in through the WM_INITDLG to the value.
  *
  * Calls:
  */

 MRESULT EXPENTRY EntryFldDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
 {
    SHORT sNewSize;                                     /* Holds new set set *
    static USHORT FAR *pcSetSize;    /* Set to point to user-passed set size *
    switch(msg)
    {
       case WM_INITDLG:
          pcSetSize=PVOIDFROMMP(mp2);
          WinSendDlgItemMsg(hwnd, ID_ENTRYFLD,EM_SETTEXTLIMIT,  /* Limit len *
                                  MPFROM2SHORT(3,0),NULL);
          WinSetDlgItemShort(hwnd, ID_ENTRYFLD,(SHORT) *pcSetSize,TRUE);
          return 0L;                           /* Allow normal focus setting *

       case WM_COMMAND:
          switch(COMMANDMSG(&msg)->cmd)
          {
             case DID_OK:
                WinQueryDlgItemShort(hwnd, ID_ENTRYFLD,
                                     &sNewSize, TRUE); /* Get the short      *
                if(sNewSize>0 && sNewSize<=NELEMS)  /* Set new data set size *
                {
                   *pcSetSize = (USHORT) sNewSize;
                   WinDismissDlg(hwnd,TRUE);
                }
                else                                   /* Invalid value      *
                   WinDismissDlg(hwnd,FALSE);
                return 0L;

             case DID_CANCEL:
                WinDismissDlg(hwnd,FALSE);
                return 0L;

             default:
                return WinDefDlgProc(hwnd, msg, mp1, mp2);
          }

       default:
          return WinDefDlgProc(hwnd, msg, mp1, mp2);
    }
 }

 /*
  * Function name: RandomizeData()
  *
  * Parameters:  cSetSize is the current size of the data set
  *
  * Returns: VOID
  *
  * Purpose: This routine randomizes the data arrays.
  *
  * Usage/Warnings: This routine assumes that LISTCNT and NELEMS define
  *                 the dimensions of the global Data array which is
  *                 randomized.
  *
  * Calls:
  */

 VOID RandomizeData(USHORT cSetSize)
 {
    USHORT cnt,cnt2;
    for(cnt=0;cnt<LISTCNT;cnt++)
       for(cnt2=0;cnt2<cSetSize;cnt2++)
          Data[cnt][cnt2] = rand();
 }




 SPY.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SPY.C

 /***************************************************************************\
 * spy.c - Spy application
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define INCL_DOSPROCESS
 #define        INCL_WINDIALOGS
 #define        INCL_WINFRAMEMGR
 #define        INCL_WINHEAP
 #define        INCL_WININPUT
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMENUS
 #define        INCL_WINMESSAGEMGR
 #define        INCL_WINPOINTERS
 #define        INCL_WINSHELLDATA
 #define        INCL_WINSYS
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "spyhook.h"
 #include <time.h>
 #include "spy.h"

 /* File Local Variables */
 HAB         hab;
 HMQ         hmqSpy;
 HWND        hwndSpy;
 HWND        hwndSpyFrame;
 HWND        hwndSpyList = NULL;
 HWND        hwndWindowLB;
 HWND        hwndMessageLB;
 HHEAP       hHeap;
 SHORT       cxBorder;
 SHORT       cyBorder;

 HPOINTER    hptrArrow;
 HPOINTER    hptrSelWin;

 USHORT      iCurItemFocus;              /* Index to item that has the focus *
 BOOL        fSpyActive = MIA_CHECKED;   /* Any non-zero is true */
 BOOL        fTrackingListBox = FALSE;   /* Tracking windows active ? */
 BOOL        fAllFrames = 0;             /* Are we processing all frames ? */
 BOOL        fAllWindows = 0;            /* Are we processing all windows ? */


 HWND        hwndWinDlgDisp = NULL;      /* hwnds info in Window Dialog */

 SHORT       wDumpCount = 0;             /* Count of which window is being dum
 SPWD        *pspwd = NULL;


 /* Define memory semaphore to have second thread sleep on */
 ULONG       semThread = 0L;             /* Thread to wait on */
 int         AboutCount = 0;
 UCHAR       rgMsgData[MAXMSGBYTES];     /* Max bytes to extract per message *


 char szSpyClass[] = "Spy";
 char szTitle[] = "";

 /* Function Prototypes */
 int        cdecl main(int, char **);
 void    FAR ProcHookThread(void);    /* will process the hook messages */
 void    ProcessQueueMsg(QMSGSPY *);
 void    UpdateMsgBoxCurMsgText(HWND);
 void    InitializeOptions(int, char **);    /* initialize Spy initial state *
 PSZ     DumpParam(PSZ prgData, MPARAM mp, SHORT cb, UCHAR bMPType);
 MRESULT EXPENTRY SpyWndProc(HWND, USHORT, MPARAM, MPARAM);
 VOID        SpyPaint(VOID);


 /***************************************************************************\
 * int cdecl main (argc, argv)
 *
 * Spy Main function
 \***************************************************************************/
 int cdecl main(argc, argv)
 int argc;
 char **argv;
 {
     ULONG   flCreateFlags;
     QMSG    qmsg;
     RECTL   rcl;
     TID     tid;
     char    *prgStack;


     hab = WinInitialize(0);

     hmqSpy = WinCreateMsgQueue(hab, 0);

     if (!WinRegisterClass((HAB)NULL, szSpyClass, (PFNWP)SpyWndProc,
             CS_SYNCPAINT, 0)) {
         WinAlarm(HWND_DESKTOP, 0xffff);
         return(0);
     }


     /*
      * Create a heap for the program
      */
     hHeap = WinCreateHeap(0, 0, 0, 0, 0, 0);

     /*
      * Create a stack for the thread - also initialize the stack by zeroing
      * the first 32 bytes, and filling the remainder with a known value
      */
     prgStack = WinAllocMem(hHeap, CBSTACK);
     if (prgStack == NULL)
         goto Abort;
     memset(prgStack, '\0', 32); /* Init first 32 bytes to zero */
     memset(prgStack+32,'\345', CBSTACK-32); /* Remainder to known value */

     hptrArrow = WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, TRUE);
     cxBorder = (SHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER);
     cyBorder = (SHORT)WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER);

     hptrSelWin = WinQuerySysPointer(HWND_DESKTOP, SPTR_MOVE, TRUE);

     SpyInstallHook(hab, hmqSpy, spyopt.bHooks);
     SpySetAllWindowOpt (fAllWindows);
     SpySetAllFrameOpt (fAllFrames);

     flCreateFlags = FCF_STANDARD;
     hwndSpyFrame = WinCreateStdWindow(HWND_DESKTOP,
             WS_VISIBLE,
             (VOID FAR *)&flCreateFlags,
             szSpyClass, szTitle,
             WS_VISIBLE,
             (HMODULE)NULL, IDR_SPY,
             (HWND FAR *)&hwndSpy);

     WinQueryWindowRect(hwndSpy, &rcl);
     hwndSpyList = WinCreateWindow (hwndSpy, WC_LISTBOX, "",
             WS_VISIBLE | LS_NOADJUSTPOS,
             -cxBorder, -cyBorder,
             (SHORT)(rcl.xRight - rcl.xLeft) + 2 * cxBorder,
             (SHORT)(rcl.yTop - rcl.yBottom) + 2 * cyBorder,
             hwndSpy, HWND_TOP, DID_SPYLIST, NULL, NULL);

     /*
      * Read the os2.ini information if it exists, and set the menu items
      * to correspond to the initial state read from OS2.INI.
      */
     InitializeOptions(argc, argv);    /* initialize Spy initial state */

     /*
      * Set the focus to the list box.  Note: Only call WinSetFocus if
      * our frame is the active window.  As we may have been started in
      * the background.  If this is the case, we want to set the frame's
      * focus save to the listbox, such that it will be the active window
      * when our frame is activated.
      */
     if (WinQueryWindow(HWND_DESKTOP, QW_TOP, FALSE) == hwndSpyFrame)
         WinSetFocus(HWND_DESKTOP, hwndSpyList);
     else
         WinSetWindowULong(hwndSpyFrame, QWL_HWNDFOCUSSAVE,
             (ULONG)hwndSpyList);



     /* Start the thread that will process the messages from the hook */
     DosCreateThread(ProcHookThread, (PTID)&tid,
         (PBYTE)(prgStack + CBSTACK - 1));

     UpdateHooksMsgTable();      /* Set Spy's Message Table */
     SpyHookOnOrOff (TRUE);      /* Turn the hook on */


    /*
     * Now process all of the messages
     */
     while (WinGetMsg(NULL, (PQMSG)&qmsg, NULL, 0, 0)) {
         WinDispatchMsg(NULL, (PQMSG)&qmsg);
     }

     SpyReleaseHook (TRUE);      /* Release input hook */

     WinDestroyWindow(hwndSpyFrame);

     WinDestroyPointer(hptrArrow);
     WinDestroyPointer(hptrSelWin);

 Abort:
     WinDestroyMsgQueue(hmqSpy);
     WinTerminate(hab);

     /* If the spy output file is open, close it now */
     if (spyopt.hfileSpy != NULL)
         DosClose(spyopt.hfileSpy);


     DosExit(EXIT_PROCESS, 0);
 }




 /***************************************************************************\
 * InitializeOptions(argc, argv)
 *
 * Initialize spy, first from the default options, second from
 * OS2.INI file, and override from command switches.
 \***************************************************************************/
 VOID InitializeOptions(argc, argv)
 int argc;
 char **argv;
 {
     USHORT      cch;
     USHORT      wAction;
     HWND        hwndMenu;


     /*
      * If the OS2.INI information exists, initialize our options to
      * the stored values.
      */
     if (WinQueryProfileSize (hab, "Spy", "Options", &cch) == 0) {
         cch = sizeof(SPYOPT);

         WinQueryProfileData(hab, "Spy", "Options", (PSZ)&spyopt,
                 &cch);
         WinQueryProfileString(hab, "Spy", "FileName", "spy.out",
                 (PSZ)spystr.szFileName, sizeof(spystr.szFileName));
         WinQueryProfileString(hab, "Spy", "SaveFileName", "spy.lis",
                 (PSZ)spystr.szSaveFileName, sizeof(spystr.szSaveFileName));
     }

     /*
      * Then check for command line overrides
      */
     while  (argc > 1) {
         argv++; /* get beyond the program name */

         /* Test for send message hook flag */
         if (!strcmpi(*argv, "+s"))
             spyopt.bHooks |= SPYH_SENDMSG;
         if (!strcmpi(*argv, "-s"))
             spyopt.bHooks &= ~SPYH_SENDMSG;

         /* Test for input hook flag */
         if (!strcmpi(*argv, "+i"))
             spyopt.bHooks |= SPYH_INPUT;
         if (!strcmpi(*argv, "-i"))
             spyopt.bHooks &= ~SPYH_INPUT;

         argc--;
     }

     /*
      * Now we need to update the menu items to the final
      * state
      */
     hwndMenu = WinWindowFromID(hwndSpyFrame, FID_MENU);

     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_INPUTHOOK, TRUE),
             MPFROM2SHORT(MIA_CHECKED,
              (spyopt.bHooks & SPYH_INPUT) ? MIA_CHECKED : 0));
     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDMSGHOOK, TRUE),
              MPFROM2SHORT(MIA_CHECKED,
              (spyopt.bHooks & SPYH_SENDMSG) ? MIA_CHECKED : 0));

     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDEXTEND, TRUE),
             MPFROM2SHORT(MIA_CHECKED,
              spyopt.fSendExtend ? MIA_CHECKED : 0));

     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDSTACK, TRUE),
             MPFROM2SHORT(MIA_CHECKED,
             spyopt.fSendStack ? MIA_CHECKED : 0));
     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_OUTSCREEN, TRUE),
             MPFROM2SHORT(MIA_CHECKED, spyopt.fWindow ? MIA_CHECKED : 0));

     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_OUTFILE, TRUE),
             MPFROM2SHORT(MIA_CHECKED, spyopt.fFile ? MIA_CHECKED : 0));

     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(CMD_ALPHASORT, TRUE),
             MPFROM2SHORT(MIA_CHECKED,
                     spyopt.fAlphaSortMsgList ? MIA_CHECKED : 0));

     /*
      * If the options specify output to file, open the file now
      */
     if (spyopt.fFile) {
         if (DosOpen((PSZ)spystr.szFileName, &spyopt.hfileSpy,
                 (USHORT far *)&wAction, 0L, 0,
                 0x0012, 0x00C1, 0L) != 0)
             spyopt.hfileSpy = NULL; /* Failed on open */
     }
 }


 /***************************************************************************\
 * MRESULT EXPENTRY SpyWndProc(hwnd, msg, mp1, mp2)
 *
 * Spy Client window procedure
 \***************************************************************************/
 MRESULT EXPENTRY SpyWndProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     QMSGSPY qmsgspy;
     SHORT   cBytes;
     USHORT      wAction;

     switch (msg) {
     case WM_CREATE:
         /* Set up this global first thing in case we need it elsewhere */
         hwndSpy = hwnd;
         break;

     case WM_SEM2:
         /*
          * Other thread told use there are some messages out there.  Loop
          * through and process all of the pending messages, and output
          * the listbox position at the end.  Also make sure to flush
          * the file buffer before we go back to sleep.
          */
         while (SpyGetNextMessage(&qmsgspy, rgMsgData, sizeof(rgMsgData), 0L))
             ProcessQueueMsg(&qmsgspy);
         }

         if (spyopt.fFile)
             DosBufReset(spyopt.hfileSpy);

         DosSemClear((HSEM)(PULONG)&semThread);

         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case CMD_ACTIVE:

             /*
              * THe active menu item was selected, we will toggle the
              * the selection by setting active to 0 or MIA_CHECKED.
              * Call the hook, and then update the checkmark on the menu
              */
             fSpyActive ^= MIA_CHECKED;  /* Toggle on or off */
             SpyHookOnOrOff (fSpyActive);
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_ACTIVE, TRUE),
                 MPFROM2SHORT(MIA_CHECKED, fSpyActive));

             break;
         case CMD_ABOUT:
             WinDlgBox(HWND_DESKTOP, hwnd, (PFNWP)AboutWndProc, NULL, AboutDlg
             break;

         case CMD_EXIT:
             WinPostMsg(NULL, WM_QUIT, 0L, 0L);
             break;

         case CMD_CLRWIN:
             /*
              * Delete all items in the list.  Simply do this
              * By deleting the first item, until the count goes to
              * zero
              */
             WinSendMsg(hwndSpyList, LM_DELETEALL, 0L, 0L);
             break;

         case CMD_SAVEWIN:
             SpyHookOnOrOff (FALSE);
             WinDlgBox(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)SpySaveListDlgProc, (HMODULE)NULL,
                 SaveListDlg, (PCH)NULL);
             SpyHookOnOrOff (fSpyActive);
             break;

         /*
          * This command saves out the current options to OS2.ini
          */
         case CMD_SAVEOPT:
             WinWriteProfileData(hab, "Spy", "Options", (PSZ)&spyopt,
                     sizeof(SPYOPT));
             WinWriteProfileString(hab, "Spy", "FileName",
                     (PSZ)spystr.szFileName);
             WinWriteProfileString(hab, "Spy", "SaveFileName",
                     (PSZ)spystr.szSaveFileName);

             break;

         case CMD_LISTNEAR:
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_LISTNEAR, TRUE),
                 MPFROM2SHORT(MIA_DISABLED, MIA_DISABLED));
             WinLoadDlg(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)ListNearDlgProc, (HMODULE)NULL,
                 ListNearDlg, (PCH)NULL);
             break;

         case CMD_WINDOWS:
             SpyHookOnOrOff (FALSE);
             hwndWindowLB = NULL;
             iCurItemFocus = (USHORT)-1;
             WinDlgBox(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)SpyWindowsDlgProc, (HMODULE)NULL,
                 WindowsDlg, (PCH)NULL);

             SpyHookOnOrOff (fSpyActive);
             break;

         case CMD_QUEUES:
             SpyHookOnOrOff (FALSE);
             hwndWindowLB = NULL;
             iCurItemFocus = (USHORT)-1;
             WinDlgBox(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)SpyQueuesDlgProc, (HMODULE)NULL,
                 MsgQueueDlg, (PCH)NULL);

             SpyHookOnOrOff (fSpyActive);
             break;

         case CMD_WNMSSEL:
         case CMD_WNMSDSL:
             SpyHookOnOrOff (FALSE);
             SelOrDeselWithMouse(SHORT1FROMMP(mp1) == CMD_WNMSSEL);
             SpyHookOnOrOff (fSpyActive);
             break;

         case CMD_ALLWNDWS:

             /*
              * The user selected the ALLFRAMES, toggle the state, and
              * update the menu and the hook state.
              */
             fAllWindows ^= MIA_CHECKED;  /* Toggle on or off */
             SpySetAllWindowOpt (fAllWindows);
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_ALLWNDWS, TRUE),
                 MPFROM2SHORT(MIA_CHECKED, fAllWindows));
             break;

         case CMD_ALLFRAMES:

             /*
              * The user selected the ALLFRAMES, toggle the state, and
              * update the menu and the hook state.
              */
             fAllFrames ^= MIA_CHECKED;  /* Toggle on or off */
             SpySetAllFrameOpt (fAllFrames);
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_ALLFRAMES, TRUE),
                 MPFROM2SHORT(MIA_CHECKED, fAllFrames));
             break;

         case CMD_WNDPWIN:
             wDumpCount = 0;
             DumpOneWindowInfo();
             break;

         case CMD_WNDPALL:
             wDumpCount = 0;
             pspwd = (SPWD *)WinAllocMem(hHeap, sizeof(SPWD)* MAXSPYDUMP);
             cBytes = DumpAllWindowsInfo(HWND_DESKTOP, 0);
             cBytes += DumpAllWindowsInfo(HWND_OBJECT, -10);
             DumpWindowIndex(cBytes);
             WinFreeMem(hHeap, (char *)pspwd, sizeof(SPWD)* MAXSPYDUMP);
             break;

         case CMD_MESSAGES:
             SpyHookOnOrOff (FALSE);
             WinDlgBox(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)SpyMsgDlgProc, (HMODULE)NULL,
                 MessagesDlg, (PCH)NULL);
             SpyHookOnOrOff (fSpyActive);
             break;

         case CMD_ALPHASORT:
             spyopt.fAlphaSortMsgList ^= TRUE;

             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                     MM_SETITEMATTR, MPFROM2SHORT(CMD_ALPHASORT, TRUE),
                     MPFROM2SHORT(MIA_CHECKED,
                             spyopt.fAlphaSortMsgList ? MIA_CHECKED : 0));
             break;

         /*
          * The command in this section are defined in the Hooks Menu.
          * All of these items toggle selections on or off.  The first two
          * items must be registered with the input hook.  The last two simply
          * retrict how much information is displayed for send messages.
          */

         case CMD_INPUTHOOK:
             spyopt.bHooks ^= SPYH_INPUT;
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_INPUTHOOK, TRUE),
                 MPFROM2SHORT(MIA_CHECKED,
                      (spyopt.bHooks & SPYH_INPUT) ? MIA_CHECKED : 0));
                 SpyReleaseHook (FALSE);     /* Dont clear queue */
                 SpyInstallHook(hab, hmqSpy, spyopt.bHooks);
             break;

         case CMD_SENDMSGHOOK:
             spyopt.bHooks ^= SPYH_SENDMSG;
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDMSGHOOK, TRUE),
                 MPFROM2SHORT(MIA_CHECKED,
                      (spyopt.bHooks & SPYH_SENDMSG) ? MIA_CHECKED : 0));
                 SpyReleaseHook (FALSE);     /* Dont clear queue */
                 SpyInstallHook(hab, hmqSpy, spyopt.bHooks);
             break;

         case CMD_SENDEXTEND:
             spyopt.fSendExtend ^= 1;  /* Toggle on or off */
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDEXTEND, TRUE),
                 MPFROM2SHORT(MIA_CHECKED,
                      spyopt.fSendExtend ? MIA_CHECKED : 0));
             break;

         case CMD_SENDSTACK:
             spyopt.fSendStack ^= 1;  /* Toggle on or off */
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_SENDSTACK, TRUE),
                 MPFROM2SHORT(MIA_CHECKED,
                     spyopt.fSendStack ? MIA_CHECKED : 0));
             break;

         /*
          * The commands in this section are defined in the Outputs Menu.
          * The first 3 items simply toggle outputs on or off, where the
          * last item allows the user to change all of the output options.
          */
         case CMD_OUTSCREEN:
             spyopt.fWindow ^= 1;  /* Toggle on or off */
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_OUTSCREEN, TRUE),
                 MPFROM2SHORT(MIA_CHECKED, spyopt.fWindow ? MIA_CHECKED : 0));
             break;

         case CMD_OUTFILE:
             spyopt.fFile ^= 1;  /* Toggle on or off */
             WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
                 MM_SETITEMATTR, MPFROM2SHORT(CMD_OUTFILE, TRUE),
                 MPFROM2SHORT(MIA_CHECKED, spyopt.fFile ? MIA_CHECKED : 0));
             /*
              * Open or close the output file
              */
             if (spyopt.fFile) {
                  if (spyopt.hfileSpy == NULL)
                     if (DosOpen((PSZ)spystr.szFileName, &spyopt.hfileSpy,
                             (USHORT far *)&wAction, 0L, 0,
                             0x0012, 0x00C1, 0L) != 0)
                         spyopt.hfileSpy = NULL; /* Failed on open */
             } else {
                 if (spyopt.hfileSpy != NULL) {
                     /* file open, not outputing, close it now */
                     DosClose (spyopt.hfileSpy);
                     spyopt.hfileSpy = NULL;
                 }
             }
             break;

         case CMD_OUTPUTS:
             SpyHookOnOrOff (FALSE);
             WinDlgBox(HWND_DESKTOP, hwndSpyFrame,
                 (PFNWP)SpyOutputsDlgProc, (HMODULE)NULL,
                 OutputsDlg, (PCH)NULL);
             SpyHookOnOrOff (fSpyActive);
             break;

         case CMD_MGDABLE:
         case CMD_MGEABLE:
             EnableOrDisableMsg(SHORT1FROMMP(mp1) == CMD_MGEABLE);
             break;
         }

         break;

     case WM_SIZE:
         /* We need to resize the listbox, if it exists */
         if (hwndSpyList != NULL) {
             WinSetWindowPos(hwndSpyList, HWND_TOP, -cxBorder, -cyBorder,
                 SHORT1FROMMP(mp2) + 2 * cxBorder,
                 SHORT2FROMMP(mp2) + 2 * cyBorder, SWP_MOVE | SWP_SIZE);
         }

         /* Now fall through to process the message */
     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }




 /***************************************************************************\
 * USHORT UConvertStringToNum(psz)
 *
 * Converts the passed string to a number 0xffff if not number
 \***************************************************************************/
 USHORT  UConvertStringToNum(psz)
 register char   *psz;
 {
     register USHORT uNum;

     /*
      * If the first few chars are 0x, we assume the user typed in a
      * HEX number, else if 0-9, we assume decimal, else we use the string.
      */
     /* First see if digit in first position */
     if ((*psz >= '0') && (*psz <= '9')) {
         /* Assume numbers now */
         if ((*psz == '0') && (*(psz+1) == 'x')) {

             /* We are in hex mode */
             psz += 2;
             uNum = 0;
             for (;;) {
                 if ((*psz >= '0') && (*psz <= '9'))
                     uNum = uNum * 16 + (USHORT)(*psz - '0');
                 else if ((*psz >= 'a') && (*psz <= 'f'))
                     uNum = uNum * 16 + (USHORT)(*psz - 'a');
                 else if ((*psz >= 'F') && (*psz <= 'F'))
                     uNum = uNum * 16 + (USHORT)(*psz - 'A');
                 else
                     break;
                 psz++;
             }

         } else {
             /* Decimal mode */
             uNum = (USHORT)(*psz++ - '0');
             while ((*psz >= '0') && (*psz <= '9')) {
                 uNum = uNum * 10 + (USHORT)(*psz++ - '0');
             }
         }

         return (uNum);
     } else
         /* Not num, return 0xffff */
         return (0xffff);
 }





 /***************************************************************************\
 * void ProcHookThread()
 *
 *   This function will wait for the hook to have messages,  when it
 *   does, it will set a memory semaphore, post a WM_SEM1 message to the other
 *   thread, and wait for the other thread has processed all of the messages.
 \***************************************************************************/

 void FAR ProcHookThread()
 {
     while (TRUE) {
         /*
          * Wait for a message to become available.
          */
         if (!SpyGetNextMessage(NULL, NULL, 0, -1L))
             break;

         /*
          * Now we have a message, set our semaphore, Post a WM_SEM2
          * message to the Client window, and wait for the client to
          * clear the semaphore.
          */
         DosSemSet((HSEM)(PULONG)&semThread);
         WinPostMsg(hwndSpy, WM_SEM2, (MPARAM)1, (MPARAM)1);
         DosSemWait((HSEM)(PULONG)&semThread, -1L);
     }

     DosExit(EXIT_THREAD, 0);
 }




 /***************************************************************************\
 * void ProcessQueueMsg(pqmsg)
 *
 *   This function will process the hook, by calling the hooks which get
 *   messages.  We will than post the message to the current output destinatio
 \***************************************************************************/

 void ProcessQueueMsg(pqmsgspy)
     QMSGSPY    *pqmsgspy;
 {
     MSGI    *pmsgi;
     SHORT   item;
     CHAR    cSource;
     CHAR    cThread;
     char    szNextMessage[100];
     char    szTime[12];
     SHORT   cch;
     CHAR    bAscii;
     PSZ     prgData;



     /*
      * Now let's build the message to output
      */
     if (WinIsWindow(hab, pqmsgspy->qmsg.hwnd)) {
         if (WinIsChild(pqmsgspy->qmsg.hwnd, hwndSpy))
             return;     /* don't want endless loops */
     }

     cThread = ':';
     if (pqmsgspy->qmsg.time == (ULONG)-1) {
         /* Sent message */
         szTime[0] = '\0';
         cSource = 'S';
         if (pqmsgspy->fs)
             cThread = '*';
     } else {
         cSource = 'I';
         if (pqmsgspy->fs != PM_REMOVE)
             cThread = '-';  /* Show different for non-remove */

         sprintf (szTime, "%-08lx", pqmsgspy->qmsg.time);
     }

     if ((pmsgi = PmsgiFromMsg(pqmsgspy->qmsg.msg)) == NULL) {
         /*
          * Message not in list, use default
          */
         cch = sprintf(szNextMessage,
             "%c%cMSG:0x%04x            H:%04x 1:%08lx 2:%08lx T:%s",
             cSource, cThread, (SHORT)pqmsgspy->qmsg.msg, (SHORT)(ULONG)pqmsgs
             pqmsgspy->qmsg.mp1, pqmsgspy->qmsg.mp2, szTime);
     } else if (pmsgi->wOptions & MSGI_MOUSE) {
         /*
          * Mouse message, decode to mouse types
          */
         cch = sprintf(szNextMessage,
             "%c%c%-20s H:%04x X:%-4d Y:%-4d HT:%04x T:%s",
             cSource, cThread, pmsgi->szMsg, (SHORT)(LONG)pqmsgspy->qmsg.hwnd,
             SHORT1FROMMP(pqmsgspy->qmsg.mp1), SHORT2FROMMP(pqmsgspy->qmsg.mp1
             SHORT1FROMMP(pqmsgspy->qmsg.mp2), szTime);
     } else if (pmsgi->wOptions & MSGI_KEY) {
         /*
          * Key messages, output special
          */
         bAscii = (CHAR)SHORT1FROMMP(pqmsgspy->qmsg.mp2);
         if ((bAscii < ' ') || (bAscii > '~'))
             bAscii = ' ';

         cch = sprintf(szNextMessage,
             "%c%c%-20s H:%04x F:%04x R:%d S:%2x C:%04x(%c) V:%02x T:%s",
             cSource, cThread, pmsgi->szMsg, (SHORT)(LONG)pqmsgspy->qmsg.hwnd,
             SHORT1FROMMP(pqmsgspy->qmsg.mp1),
             CHAR3FROMMP(pqmsgspy->qmsg.mp1),  CHAR4FROMMP(pqmsgspy->qmsg.mp1)
             SHORT1FROMMP(pqmsgspy->qmsg.mp2), bAscii,
             SHORT2FROMMP(pqmsgspy->qmsg.mp2), szTime);
     } else {
         /* No special format */
         cch = sprintf(szNextMessage,
             "%c%c%-20s H:%04x 1:%08lx 2:%08lx T:%s",
             cSource, cThread, pmsgi->szMsg, (SHORT)(ULONG) pqmsgspy->qmsg.hwn
             pqmsgspy->qmsg.mp1, pqmsgspy->qmsg.mp2, szTime);
     }

     OutputString(szNextMessage, cch);

     /*
      * Now dump out any additional information associated with the
      * message.  The processing depends of the type of message on
      * how we are going to process the data.
      */
     if (spyopt.fSendExtend) {
         prgData = DumpParam((PSZ)rgMsgData, pqmsgspy->qmsg.mp1,
                 pqmsgspy->cbDataMP1, pqmsgspy->bMPType);
         DumpParam(prgData, pqmsgspy->qmsg.mp2, pqmsgspy->cbDataMP2,
                 (UCHAR)((pqmsgspy->bMPType) >> 3));
     }

     /*
      * If this is a send message, also display the call stack information
      * of who called WinSendMsg
      */
     if (spyopt.fSendStack && (pqmsgspy->qmsg.time == (ULONG)-1)) {
         cch = sprintf(szNextMessage,
             "    PID: %-3d TID: %-2d   Stack:",
             pqmsgspy->pidSend, pqmsgspy->tidSend);

         /* Now loop and add the stack info */
         for (item=0; (item < MAXSTRACE) &&
                 (pqmsgspy->pvoidStack[item] != NULL); item++) {
             cch += sprintf(szTime, " %p", pqmsgspy->pvoidStack[item]);
             strcat(szNextMessage, szTime);
         }

         OutputString(szNextMessage, cch);
     }

 }




 /***************************************************************************\
 * PSZ DumpParam(PSZ prgData, MPARAM mp, SHORT cb, UCHAR bMPType)
 *
 * Dump the additional information that was captured for the message.
 *   using the currently defined types.
 *
 * Returns: PSZ - Pointer to next available byte after process DATA
 \***************************************************************************/
 PSZ DumpParam(prgData, mp, cb, bMPType)
 PSZ         prgData;
 MPARAM      mp;
 SHORT       cb;
 UCHAR       bMPType;
 {
     char    szNextMessage[100];
     SHORT   cch;


     if (FGuessValidPointer((PSZ)mp, cb)) {
         /* Process by type */
         switch (bMPType & 0x07) {
         case MPT_SWP:
             cch = sprintf(szNextMessage,
                 "    SWP:               fs:%04x cx:%d cy:%d y:%d x:%d HB:%04x
                 ((PSWP)prgData)->fs, ((PSWP)prgData)->cy, ((PSWP)prgData)->cx
                 ((PSWP)prgData)->y, ((PSWP)prgData)->x,
                 (SHORT)(ULONG)((PSWP)prgData)->hwndInsertBehind,
                 (SHORT)(ULONG)((PSWP)prgData)->hwnd);

             break;

         case MPT_RECTL:
             cch = sprintf(szNextMessage,
                 "    RECTL:             xLeft:%d yBottom:%d xRight:%d yTop:%d
                 ((PRECTL)prgData)->xLeft, ((PRECTL)prgData)->yBottom,
                 ((PRECTL)prgData)->xRight, ((PRECTL)prgData)->yTop);
             break;

         case MPT_QMSG:
             cch = sprintf(szNextMessage,
                 "    QMSG: H:%04x       M:%04x M1:%08lx M2:%08lx T:%08lx (%d,
                 (SHORT)(ULONG)((PQMSG)prgData)->hwnd, ((PQMSG)prgData)->msg,
                 ((PQMSG)prgData)->mp1,((PQMSG)prgData)->mp2,
                 ((PQMSG)prgData)->time,
                 ((PQMSG)prgData)->ptl.x, ((PQMSG)prgData)->ptl.y);
             break;

         default:
             goto NoData;
         }

         OutputString(szNextMessage, cch);
     }
 NoData:
     return (prgData + cb);
 }




 /***************************************************************************\
 * void OutputString(char szOut, SHORT cch);
 *
 *   This function will output the specified string to the
 *   destinations.
 \***************************************************************************/

 void OutputString(szOut, cch)
 char        szOut[];
 SHORT       cch;
 {
     SHORT   item;
     char    *psz;
     USHORT  cchWritten;



     /* Now display the new line on the screen */
     if (spyopt.fWindow) {
         item = SHORT1FROMMR(WinSendMsg(hwndSpyList, LM_INSERTITEM,
             (MPARAM)LIT_END, (MPARAM)(PSZ)szOut));

         WinSendMsg(hwndSpyList, LM_SETTOPINDEX, (MPARAM)item, 0L);

         /* See if we have too many lines now */
         while (item >= spyopt.cWindowLines)
             item = SHORT1FROMMR(WinSendMsg(hwndSpyList, LM_DELETEITEM,
                     (MPARAM)0, 0L));
     }

     /* now for file need cr/lf */
     psz = szOut + cch;    /* point to trailing null */
     *psz++ = '\r';
     *psz++ = '\n';
     *psz = '\0';

     if (spyopt.fFile)
         DosWrite(spyopt.hfileSpy, (PSZ)szOut, cch+2,
                 (PUSHORT)&cchWritten);

 }


 /***************************************************************************\
 * MSGI  * PmsgiFromMsg(USHORT msg)
 *
 * Locate the msg in the array of message items
 *
 * Returns: pointer to item that has the specified msg, or NULL
 \***************************************************************************/
 MSGI *PmsgiFromMsg(msg)
 USHORT msg;
 {
     register MSGI   *pmsgi = rgmsgi;    /* Start at beginning */
     register SHORT i;

     /*
      * Currently is a simple linear search, should be made faster
      * probably by using binary search.
      */
     for (i=0; i< cmsgi; i++) {
         if (pmsgi->msg == msg)
             return (pmsgi);
         if (pmsgi->msg > msg)
             return (NULL);
         pmsgi++;
     };

     return (NULL);
 }


 SPYHOOK.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SPYHOOK.C

 /***********************************************************************\
 * spyhook.c - Spy global input hook interface library
 * Created by Microsoft Corporation, 1989
 *
 * This dynlink is needed because global input hooks must reside in a DLL.
 \***********************************************************************/
 #define INCL_DOSPROCESS
 #define        INCL_WINHEAP
 #define        INCL_WINHOOKS
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include "spyhook.h"

 ULONG spyhookSem = 0L;          /* global semaphore for this module */
 HMODULE hmodSpy;                /* dynlink module handle            */
 SHORT    SpybHooks;             /* Which hook do we use?            */

 HAB habOwner = (HAB)0;          /* Hook owner's anchor block        */
 HMQ hmqOwner = (HMQ)0;          /* Hook owner's message queue       */
 BOOL fRecording = FALSE;        /* Are we recording now?            */
 BOOL fAnythingToWatch = FALSE;  /* Is there anything to watch?      */
 BOOL fDispMsgsNotInList = TRUE; /* Disp msgs not in list?           */

 /* Define array of window handles that we are going to process */
 BOOL fProcessAllFrames = FALSE; /* Are we processing all frame messages?*/
 BOOL fProcessAllWindows = FALSE;/* Are we processing all windows?*/
 HWND *pahwndSpy = NULL;         /* Array of hwnds to spy on, no max here */
 int  chwndSpy = 0;                  /* Max windows to spy */

 /* Define array of HMQ's to spy on */
 HMQ *pahmqSpy = NULL;           /* HMQ list to spy on */
 int chmqSpy = 0;                /* Count of hmqs we are spying on */


 /* Define message filter array */
 UCHAR rgMessageFilter[MAXMSGFILTERBYTES] = "";
 UCHAR  GMsgEnableAndType(USHORT);       /* Is message in message list?*/
 VOID   GrabMsgDataWords(SHORT, UCHAR);  /* Get extra words for message */
 VOID   CopyStruct(UCHAR FAR *pbSrc, UCHAR FAR *pbDst, SHORT cb);/* Copy bytes

 /* Define an array of messages to pass to whomever calls us */
 ULONG spyMsgSem =  0L;          /* global semaphore for this module */

 QMSGSPY  rgqmsgSpy[MAXMSGCNT];  /* Array of messages to process */
 SHORT cmsgSpy = 0;              /* Count of messages yet to be read */
 SHORT imsgWrite = 0;            /* Index to message to write out */
 SHORT imsgRead = 0;             /* Index to message to read */

 HHEAP hheapSpyHook;             /* Heap for SpyHook DLL */
 SHORT acbMPTypes[MP_MASK+1] = { /* Number of desired bytes to extract */
     0,                          /* Normal fields, no pointers */
     sizeof(SWP),                /* MPT_SWP */
     sizeof(RECTL),              /* MPT_RECTL */
     0,                          /* MPT_WNDPRMS we don't process yet */
     sizeof(QMSG)                /* MPT_QMSG */
 };


 /* Information for getting selector information in different PID context */
 PID     pidLNSpy = 0xffff;      /* Which process */
 USHORT  selectorLNSpy = 0;      /* Which Selector */
 WHOISINFO   whoIsLNSpy;         /* Symbol information */
 int     rcLNSpy = -1;           /* Return code */


 /* Define all external functions */
 extern VOID  BuildStackTrace(CHAR FAR *, SHORT, SHORT);

 /* Define all function prototypes */
 VOID SpyCheckPIDForSymbol(VOID);
 BOOL FAR PASCAL SpyInputHook(HAB, PQMSG, USHORT);
 BOOL FAR PASCAL SpySendMsgHook(HAB, PSMHSTRUCT, USHORT);
 VOID PASCAL Init(HMODULE);

 /***********************************************************************\
 * BOOL FAR PASCAL SpyInputHook( hab, lpqmsg, fs )
 * HAB hab;
 * PQMSG  lpqmsg;
 * USHORT    fs;
 *
 * Effect:  This is the global input hook procedure.  Note that hook
 *   procedures can be chained, so we always return FALSE to pass the
 *   message to the next guy in the chain.
 *
 * Return value:  FALSE to pass message to next hook procedure.
 \***********************************************************************/
 BOOL FAR PASCAL SpyInputHook( hab, lpqmsg, fs )
 HAB        hab;
 PQMSG        lpqmsg;
 USHORT  fs;
 {
     UCHAR       bMPType;

     /*
      * Check first to see if we are looking for a process's symbols
      */
     if (pidLNSpy != 0xffff)
         SpyCheckPIDForSymbol();

     /*
      * First check to see if we are processing any hook messages
      */
     if (!fRecording || !fAnythingToWatch || (cmsgSpy >= MAXMSGCNT))
         return (FALSE); /* No, let the next one have it now */

     /*
      * See if we have any messages to process - re-check to make sure we
      * don't overwrite our queue.
      */
     if (DosSemRequest((HSEM)(PULONG)&spyhookSem, 100L) != 0)
         return (FALSE);
     if (cmsgSpy < MAXMSGCNT) {
         if (SpyFWindowInList(lpqmsg->hwnd, FALSE) &&
                 (bMPType = GMsgEnableAndType(lpqmsg->msg))) {
             rgqmsgSpy[imsgWrite].fs = fs;       /* Save the flags */
             rgqmsgSpy[imsgWrite].qmsg = *lpqmsg;   /* Save the message */

             GrabMsgDataWords(imsgWrite, bMPType);
             imsgWrite++;

             if (imsgWrite == MAXMSGCNT)
                 imsgWrite = 0;  /* Wrap around */
             cmsgSpy++;
             DosSemClear((HSEM)(PULONG)&spyMsgSem);
         }
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );
     return FALSE;   /* Let system take normal action */
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpySendMsgHook( hab, lpsmh, fInterTask )
 * HAB                hab;
 * PSMHSTRUCT        lpsmh;
 * BOOL                fInterTask;
 *
 * Effect:  This is the global input hook procedure.  Note that hook
 *   procedures can be chained, so we always return FALSE to pass the
 *   message to the next guy in the chain.
 *
 * Return value:  FALSE to pass message to next hook procedure.
 \***********************************************************************/
 BOOL FAR PASCAL SpySendMsgHook( hab, lpsmh, fInterTask )
 HAB                hab;
 PSMHSTRUCT      lpsmh;
 BOOL            fInterTask;
 {
     UCHAR           bMPType;
     PIDINFO         pidinfo;

     /*
      * Check first to see if we are looking for a process's symbols
      */
     if (pidLNSpy != 0xffff)
         SpyCheckPIDForSymbol();

     /*
      * First check to see if we are processing any hook messages
      */
     if (!fRecording || !fAnythingToWatch || (cmsgSpy >= MAXMSGCNT))
         return (FALSE); /* No, let the next one have it now */

     /*
      * See if we have any messages to process
      */
     if (DosSemRequest((HSEM)(PULONG)&spyhookSem, 100L) != 0)
         return (FALSE);
     /* Make sure no one got in by mistake */
     if (cmsgSpy < MAXMSGCNT) {
         if (SpyFWindowInList(lpsmh->hwnd, FALSE) &&
                 (bMPType = GMsgEnableAndType(lpsmh->msg))) {
             /*
              * Store out message, must move pieces separately
              */
             rgqmsgSpy[imsgWrite].fs = fInterTask;
             rgqmsgSpy[imsgWrite].qmsg.hwnd = lpsmh->hwnd;
             rgqmsgSpy[imsgWrite].qmsg.msg  = lpsmh->msg;
             rgqmsgSpy[imsgWrite].qmsg.mp1  = lpsmh->mp1;
             rgqmsgSpy[imsgWrite].qmsg.mp2  = lpsmh->mp2;
             rgqmsgSpy[imsgWrite].qmsg.time = (ULONG)-1;
             rgqmsgSpy[imsgWrite].qmsg.ptl.x  = 0;
             rgqmsgSpy[imsgWrite].qmsg.ptl.y  = 0;

             GrabMsgDataWords(imsgWrite, bMPType);

             /* Now get the PID/TID info, and do the stack backtrace */
             DosGetPID(&pidinfo);
             rgqmsgSpy[imsgWrite].pidSend = pidinfo.pid;
             rgqmsgSpy[imsgWrite].tidSend = pidinfo.tid;
             BuildStackTrace((CHAR FAR *)rgqmsgSpy[imsgWrite].pvoidStack,
                     CALLSTOSKIP, MAXSTRACE);

             imsgWrite++;
             if (imsgWrite == MAXMSGCNT)
                 imsgWrite = 0;  /* Wrap around */
             cmsgSpy++;
             DosSemClear((HSEM)(PULONG)&spyMsgSem);
         }
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );
     return FALSE;   /* Let system take normal action */
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyInstallHook( hab, hmq, fSendMessage, bHooks)
 *
 * Effect:  This routine installs a system-wide HK_INPUT hook.  The hab
 *   hmq are remembered for message posting.  Note that we only allow
 *   one input hook to be installed through this routine, but other
 *   apps may call WinSetHook directly.
 *
 * Returns value:  TRUE if hook installed successfully, FALSE otherwise.
 \***********************************************************************/
 BOOL FAR PASCAL SpyInstallHook( hab, hmq, bHooks )
 HAB     hab;
 HMQ     hmq;
 USHORT  bHooks;
 {
     BOOL fRet;
     SEL  selHeap;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     /*
      * If this is the first time through, we need to create a local heap
      * to store message information.
      */
     if (hheapSpyHook == NULL) {
         selHeap = (SEL)(((ULONG)((PSZ)&hheapSpyHook)) >> 16);
         hheapSpyHook = WinCreateHeap(selHeap, 1024, 0, 0, 100,
             HM_MOVEABLE | HM_VALIDSIZE);
         if (hheapSpyHook == NULL)
             return (FALSE);
     }



     /*
      * Look at hook index.
      *
      * if it equals  HK_INPUT then
      *                install a system-wide input hook
      * else
      *                set system wide SENDMSG hook
      */
     SpybHooks = bHooks;
     if (SpybHooks & SPYH_INPUT)
         fRet = WinSetHook( hab, (HMQ)0, HK_INPUT, (PFN)SpyInputHook,
                     hmodSpy );

     if (SpybHooks & SPYH_SENDMSG)
         fRet = WinSetHook( hab, (HMQ)0, HK_SENDMSG, (PFN)SpySendMsgHook,
                     hmodSpy );
     if (fRet) {
         habOwner = hab;
         hmqOwner = hmq;
     }

     DosSemSet((HSEM)(PULONG)&spyMsgSem);    /* Init, no messages avail */
     DosSemClear( (HSEM)(PULONG)&spyhookSem );

     return fRet;
 }



 /***********************************************************************\
 * BOOL FAR PASCAL SpySetWindowList( chwnd, rghwnd)
 *
 * Effect:  This routine sets the list of window that we are interested
 *   in watching the messages for.
 *
 * Returns value:  TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpySetWindowList( chwnd, rghwnd )
 SHORT       chwnd;
 HWND FAR    *rghwnd;
 {
     SHORT   i;
     HWND    *phwndT;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     if (pahwndSpy) {
         WinFreeMem(hheapSpyHook, (char *)pahwndSpy, sizeof(HWND) * chwndSpy);
         pahwndSpy = NULL;
     }


     chwndSpy = chwnd;

     if (chwndSpy > 0 ) {

         phwndT = pahwndSpy = (HWND *) WinAllocMem(hheapSpyHook,
                 sizeof(HWND) * chwndSpy);

         if (phwndT != NULL) {
             for (i=0; i < chwnd; i++) {
                 *phwndT++ = *rghwnd++;
             }
         } else
             chwndSpy = 0;
     }

     fAnythingToWatch = (chwndSpy > 0) || (chmqSpy > 0) ||
             fProcessAllWindows || fProcessAllFrames;

     DosSemClear( (HSEM)(PULONG)&spyhookSem );
     return(TRUE);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyGetWindowList( chwnd, rghwnd)
 *
 * Effect:  This routine sets the list of window that we are interested
 *   in watching the messages for.
 *
 * Returns value:  TRUE
 \***********************************************************************/
 SHORT FAR PASCAL SpyGetWindowList( chwnd, rghwnd )
 SHORT       chwnd;
 HWND FAR    *rghwnd;
 {
     SHORT   i;
     HWND    *phwndT;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     if (chwnd > chwndSpy)
             chwnd = chwndSpy;

     phwndT = pahwndSpy;
     for (i=0; i < chwnd; i++) {
         *rghwnd++ = *pahwndSpy++;
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );

     return(chwnd);
 }




 /***********************************************************************\
 * BOOL FAR PASCAL SpySetQueueList( chmq, rghmq )
 *
 * Effect:  This routine sets the list of queues whose messages we watch.
 *
 * Returns value:  TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpySetQueueList( chmq, rghmq )
 SHORT       chmq;
 HMQ FAR    *rghmq;
 {
     SHORT   i;
     HMQ     *phmqT;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     /*
      * If we previously had a list of HMQs to spy on, free the old list
      * now
      */
     if (pahmqSpy != NULL) {
         WinFreeMem(hheapSpyHook, (char *)pahmqSpy, sizeof(HMQ) * chmqSpy);
         pahmqSpy = NULL;
     }

     chmqSpy = chmq;

     if (chmqSpy > 0) {
         phmqT = pahmqSpy = (HWND *) WinAllocMem(hheapSpyHook, sizeof(HMQ) * c

         if (phmqT != NULL) {
             for (i=0; i < chmq; i++) {
                 *phmqT++ = *rghmq++;
             }
         } else
             chmqSpy = 0;
     }

     fAnythingToWatch = (chwndSpy > 0) || (chmqSpy > 0) ||
             fProcessAllWindows || fProcessAllFrames;
     DosSemClear( (HSEM)(PULONG)&spyhookSem );

     return(TRUE);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyGetQueueList( chmq, rghmq )
 *
 * Effect:  This routine gets the list of queues whose messages we watch.
 *
 * Returns value:  TRUE
 \***********************************************************************/
 SHORT FAR PASCAL SpyGetQueueList( chmq, rghmq )
 SHORT       chmq;
 HMQ FAR    *rghmq;
 {
     SHORT   i;
     HMQ     *phmqT;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     if (chmq > chmqSpy)
         chmq = chmqSpy;

     phmqT = pahmqSpy;

     for (i=0; i < chmq; i++) {
         *rghmq++ = *phmqT++;
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );

     return(chmq);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpySetMessageList(UCHAR FAR *prgNewMsgFilter,
 *                   BOOL fNewDispMsgsNotInList)
 *
 * Effect:  This routine sets the list of windows that whose message we watch.
 *
 * Returns value:  TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpySetMessageList(prgNewMsgFilter, fNewDispMsgsNotInList)
 UCHAR FAR           *prgNewMsgFilter;
 BOOL                fNewDispMsgsNotInList;
 {
     SHORT           i;
     unsigned char   *prgb;

     DosSemRequest( (HSEM)(PULONG)&spyhookSem, -1L);

     prgb = rgMessageFilter;


     fDispMsgsNotInList = fNewDispMsgsNotInList;

     for (i=0; i < MAXMSGFILTERBYTES; i++) {
         *prgb++ = *prgNewMsgFilter++;
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );

     return(TRUE);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyFWindowInList (hwnd, fInWindowListOnly)
 *
 * Effect:  This function checks our current list of windows, and returns
 *          TRUE if the window is in the list, else returns FALSE.
 *
 * Returns value:  TRUE if window is in list.  Also, if we are in the special
 *           state we will pass through all frame windows.
 \***********************************************************************/
 BOOL FAR PASCAL SpyFWindowInList (hwnd, fInWindowListOnly)
 register HWND  hwnd;
 BOOL            fInWindowListOnly;
 {
     register int    i;
     char            szClassName[10];    /* Class name of window */
     CLASSINFO       classinfo;          /* Information about class */
     HMQ             hmqWindow;          /* HMQ of window        */
     HMQ             *phmqT;             /* Temporary pointer in hmq list */
     HWND            *phwndT;            /* Temporary pointer to HWND list */

     phwndT = pahwndSpy;
     for (i=0; i < chwndSpy; i++) {
         if (hwnd == *phwndT++)
             return (TRUE);
     }

     /* See if we are restricting to only windows in list */
     if (fInWindowListOnly)
         return (FALSE);

     if (fProcessAllWindows)
         return (TRUE);      /* All windows pass through */

     /*
      * See if we are watching any message queues
      */
     if (phmqT = pahmqSpy) {
         hmqWindow = (HMQ)WinQueryWindowULong(hwnd, QWL_HMQ);
         for (i=0; i < chmqSpy; i++) {
             if (*phmqT++ == hmqWindow)
                 return (TRUE);
         }
     }

     if (fProcessAllFrames) {
         /* See if frame class */
         if (hwnd == NULL)
             return (TRUE);      /* pass queue messages through */
         WinQueryClassName(hwnd, sizeof(szClassName),
             (PSZ)szClassName);
         WinQueryClassInfo((HAB)NULL, (PSZ)szClassName,
             &classinfo);
         if (classinfo.flClassStyle & CS_FRAME)
             return (TRUE);
     }

     return (FALSE);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyReleaseHook(fZeroQueue)
 *
 * Effect:  This routine releases the input hook, if it is installed.
 *
 * Returns value: TRUE if hook is released, FALSE otherwise.
 \***********************************************************************/
 BOOL FAR PASCAL SpyReleaseHook(fZeroQueue)
 BOOL    fZeroQueue;
 {
     BOOL fRet;

     DosSemRequest ( (HSEM)(PULONG)&spyhookSem, -1L );

     if ( habOwner ) {
         if (SpybHooks & SPYH_INPUT)
                 fRet = WinReleaseHook( habOwner, (HMQ)0, HK_INPUT,
                     (PFN)SpyInputHook, hmodSpy );

         if (SpybHooks & SPYH_SENDMSG)
                 fRet = WinReleaseHook( habOwner, (HMQ)0, HK_SENDMSG,
                     (PFN)SpySendMsgHook, hmodSpy );
             if ( fRet ) {
                 habOwner = (HAB)0;
                 hmqOwner = (HMQ)0;
         }
     }

     /*
      * When the hook is freed, we want to clear message count out,
      * make sure any process waiting will abort
      * Only do this if the Zeroqueu flag was passed
      */
     if (fZeroQueue) {
         cmsgSpy = 0;
         imsgWrite = 0;
         imsgRead = 0;
         if (pahwndSpy != NULL)
             WinFreeMem(hheapSpyHook, (char *)pahwndSpy,
                     sizeof(HWND) * chwndSpy);
         pahwndSpy = NULL;
         chwndSpy = 0;           /* Max windows to spy */
         fRecording = FALSE;     /* Set recording to off */
         if (pahmqSpy != NULL)
             WinFreeMem(hheapSpyHook, (char *)pahmqSpy, sizeof(HMQ) * chmqSpy)

         pahmqSpy = NULL;
         chmqSpy = 0;
         fAnythingToWatch = FALSE;
         DosSemClear((HSEM)(PULONG)&spyMsgSem);    /* Free any process */
     }
     DosSemClear((HSEM)(PULONG)&spyhookSem);

     return fRet;
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpyHookOnOrOff( fOn)
 *
 * Effect:  This routine allows the application to turn the hook
 *   processing on or off.
 *
 * Returns:        TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpyHookOnOrOff(fOn)
 BOOL fOn;
 {
     DosSemRequest ( (HSEM)(PULONG)&spyhookSem, -1L);

     fRecording = fOn;

     DosSemClear( (HSEM)(PULONG)&spyhookSem );
     return (TRUE);
 }



 /***********************************************************************\
 * BOOL FAR PASCAL SpySetAllFrameOpt(fAllFrames)
 *
 * Effect:  Special state if TRUE will cause the window filter to pass
 *       all frame windows through.  This is useful when debugging
 *       interactions between frame windows, when the windows are
 *       net yet created.
 *
 * Returns:        TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpySetAllFrameOpt(fAllFrames)
 BOOL fAllFrames;
 {
     fProcessAllFrames = fAllFrames;
     fAnythingToWatch = (chwndSpy > 0) || (chmqSpy > 0) ||
             fProcessAllWindows || fProcessAllFrames;
     return (TRUE);
 }

 /***********************************************************************\
 * BOOL FAR PASCAL SpySetAllWindowOpt(fAllWindows)
 *
 * Effect:  Special state if TRUE will cause the window filter to pass
 *       all windows through.  This is useful when debugging
 *       interactions between all of the windows.
 *
 * Returns:        TRUE
 \***********************************************************************/
 BOOL FAR PASCAL SpySetAllWindowOpt(fAllWindows)
 BOOL fAllWindows;
 {
     fProcessAllWindows = fAllWindows;
     fAnythingToWatch = (chwndSpy > 0) || (chmqSpy > 0) ||
             fProcessAllWindows || fProcessAllFrames;
     return (TRUE);
 }


 /***********************************************************************\
 * BOOL FAR PASCAL SpySetLNSymbolPID(PID pid, SELECTOR sel);
 *
 *
 * Effect:  Tell the hook to be on the lookout for a specific PID.
 *       If found running in the context of the specified process, we
 *       will get the selector information for the specified selector.
 \***********************************************************************/
 BOOL FAR PASCAL SpySetLNSymbolPID(pid, selector)
 PID     pid;
 USHORT  selector;
 {
     if (pidLNSpy == 0xffff) {
         selectorLNSpy = selector;
         rcLNSpy = -1;               /* Assume error for now */
         pidLNSpy = pid;             /* Save PID to look for */
         return (TRUE);
     } else
         return (FALSE);
 }



 /***********************************************************************\
 * VOID SpyCheckPIDForSymbol(VOID)
 *
 * Effect:  Only called when we are looking for a symbol
 *
 \***********************************************************************/
 VOID SpyCheckPIDForSymbol()
 {
     PIDINFO         pidinfo;

     /*
      * Get the spy semaphore to serialize access and see if we are the
      * correct PID;
      */
     DosGetPID(&pidinfo);
     if (DosSemRequest((HSEM)(PULONG)&spyhookSem, 100L) != 0)
         return;

     if (pidinfo.pid == pidLNSpy) {
         /*
          * We are the correct process, so continue to try go get the
          * symbol information.
          */
         pidLNSpy = 0xffff;       /* reset to not enter again */
         rcLNSpy = IdentifyCodeSelector( selectorLNSpy,
             (PWHOISINFO)&whoIsLNSpy);
     }

     DosSemClear( (HSEM)(PULONG)&spyhookSem );
 }



 /***********************************************************************\
 * int FAR PASCAL SpyGetLNSymbolSelector(pwhoinfo)
 *
 * Effect:  Get the information about the specified selector.
 *       If the specified process has not been executed since the
 *       function SpySetLNSymbolPID has been called, it will return -1,
 *       else it will return what was returned from IdentifyCodeSelector.
 \***********************************************************************/
 int FAR PASCAL SpyGetLNSymbolSelector(pwhoIsinfo)
 PWHOISINFO    pwhoIsinfo;
 {
     *pwhoIsinfo = whoIsLNSpy;
     return (rcLNSpy);
 }




 /**********************************************************************\
 * VOID GrabMsgDataWords (SHORT imsg, UCHAR bMPType)
 *
 * Effect:  Gets the next message from the list.
 *
 *        if timeout != 0 on message processing threads, problems may occur.
 *        if lpqmsg==NULL, this function acts like a query or wait function.
 \***********************************************************************/
 VOID GrabMsgDataWords(imsg, bMPType)
 SHORT       imsg;
 UCHAR       bMPType;
 {
     UCHAR   bMP1Type;
     UCHAR   bMP2Type;
     SHORT   cbMPs;
     SHORT   cbMP1;
     SHORT   cbMP2;
     UCHAR   *rgBuf;

     /*
      * This function will use the MPType data to know if MP1 and/or MP2
      * are pointers to any known data, that we want to extract off
      * and save for spy to display later
      */
     rgqmsgSpy[imsg].bMPType = bMPType;
     bMP1Type = (UCHAR)(bMPType & MP_MASK);
     cbMPs = cbMP1 = rgqmsgSpy[imsg].cbDataMP1 = acbMPTypes[bMP1Type];

     bMP2Type = (UCHAR)((bMPType >> 3) & MP_MASK);
     cbMPs += (cbMP2 = rgqmsgSpy[imsg].cbDataMP2 = acbMPTypes[bMP2Type]);

     if (cbMPs > 0) {
         /* Allocate memory to save the data into */
         rgBuf = rgqmsgSpy[imsg].rgData = WinAllocMem(hheapSpyHook, cbMPs);

         if (rgBuf != NULL) {
             /* Copy the data down, Note: if count is 0 will NOP */
             if (cbMP1) {
                 CopyStruct((CHAR FAR *)rgqmsgSpy[imsg].qmsg.mp1,
                         rgBuf, cbMP1);
                 rgBuf += cbMP1;
             }

             if (cbMP2) {
                 CopyStruct((CHAR FAR *)rgqmsgSpy[imsg].qmsg.mp2,
                         rgBuf, cbMP2);
             }
         }

     } else {
         rgqmsgSpy[imsg].rgData = NULL;
     }
 }

 /**********************************************************************\
 * SpyGetNextMessage (lpqmsg, lpBuf, cbBuf, lTimeOut)
 *
 * Effect:  Get the next message from the list.
 *         if timeout != 0 on message processing threads, problems may occur.
 *        if lpqmsg==NULL, this function acts like a query or wait function.
 *
 * Returns:        Whether SpyGetNextMessage succeeds or not.
 \***********************************************************************/
 BOOL FAR PASCAL SpyGetNextMessage(lpqmsg, lpBuf, cbBuf, lTimeOut)
 PQMSGSPY   lpqmsg;      /* Pointer where the user wants the message stored */
 PSZ        lpBuf;       /* pointer to buffer */
 SHORT      cbBuf;       /* size of buffer in bytes */
 LONG    lTimeOut;       /* Timeout value */
 {

     SHORT       cbMsg;  /* Count of bytes associated with message */

     /* Quick escape hatch */
     if ((lTimeOut == 0) && (cmsgSpy == 0))
         return (FALSE);     /* Don't Wait */

     /*
      * Now let's possibly wait for a message
      */
     if (cmsgSpy == 0) {
         if (DosSemWait((HSEM)(PULONG)&spyMsgSem, lTimeOut) != 0)
             return (FALSE); /* No messages after timeout */
         if (cmsgSpy == 0)
             return (FALSE); /* Still no messages, return condition */
     }

     /*
      * If the lpqmsg is NULL, the user is simply asking if there is
      * a message and/or waiting for the message, so don't extract
      * the message, but simply return the status.
      */
     if (lpqmsg != NULL) {
         if (DosSemRequest((HSEM)(PULONG)&spyhookSem, lTimeOut) == 0) {
             *lpqmsg = rgqmsgSpy[imsgRead];   /* Extract the message */

             cbMsg = rgqmsgSpy[imsgRead].cbDataMP1
                     + rgqmsgSpy[imsgRead].cbDataMP2;

             if ((cbMsg > 0) && (lpBuf != NULL)) {
                 if (cbMsg < cbBuf)
                     cbBuf = cbMsg;  /* Number of bytes to copy */
                 CopyStruct(rgqmsgSpy[imsgRead].rgData, lpBuf, cbBuf);
             }

             if (cbMsg > 0)
                 WinFreeMem(hheapSpyHook, rgqmsgSpy[imsgRead].rgData, cbMsg);

             /* Also give the caller any additional information on message */

             imsgRead++;
             if (imsgRead == MAXMSGCNT)
                 imsgRead = 0;  /* Wrap around */

             /*
              * Decrement count of messages, if we go to zero, set
              * the semaphore, so that the next read will suspend until
              * the next message
              */
             cmsgSpy--;
             if (cmsgSpy == 0)
                 DosSemSet((HSEM)(PULONG)&spyMsgSem);

             DosSemClear( (HSEM)(PULONG)&spyhookSem );
         }
     }

     return (TRUE);
 }

 /***********************************************************************\
 * VOID PASCAL Init( hmod )
 *
 * Effect:        Saves the Spy module handle
 \***********************************************************************/
 VOID PASCAL Init( hmod )
 HMODULE hmod;
 {
     /* Save the module handle */
     hmodSpy = hmod;
 }

 /***********************************************************************\
 * UCHAR GMsgEnableAndType (USHORT msg)
 *
 * Effect:
 *   Should the message be processed?
 *
 *   If the message is out of range
 *        or bit is set in message bitmask then return TRUE;
 \***********************************************************************/
 UCHAR GMsgEnableAndType(msg)
 USHORT  msg;
 {
     if (msg > MAXMSGFILTER)
         return ((UCHAR)(fDispMsgsNotInList? MP_ENABLED : 0));

     return (rgMessageFilter[msg]);   /* One byte per message */
 }


 SPYHSTRT.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SPYHSTRT.ASM

 ;  SPYHOOK library initialization routine
 ;  Created by Microsoft Corporation, 1989

 ?WIN=1            ; Use Windows prolog/epilog
 ?PLM=1            ; Use PLM calling convention
 DOS5=1
 .xlist
 include cmacros.inc
 .list

 ; Define some constants to help build stack trace
 savedCS = 4
 savedIP = 2
 savedBP = 0
 savedDS = -2

 sBegin        DATA
 assumes DS,DATA
 public        __acrtused
 __acrtused = 1
 sEnd        DATA

 sBegin        CODE
 assumes cs,CODE
 assumes ds,DATA

 EXTRN        Init:NEAR
 .286p

 ;
 ; Registers set up by DosLoadModule...
 ;
 ;   SI = heap size
 ;   DI = module ID
 ;   DS = library's automatic data segment
 ;
 cProc        LoadProc,<FAR,PUBLIC>
 cBegin        LoadProc

         push di
         call Init              ; Init( hmod );

 cEnd        LoadProc



 ; CopyStruct
 ;
 ; CopyStruct(pbSrc, pbDst, cb)
 ;
 cProc        CopyStruct,<NEAR,PUBLIC>,<DS,SI,DI>
 ParmD  pbSrc
 ParmD  pbDest
 ParmW  cb
 cBegin

         mov        cx,cb
         jcxz        lcopydone            ; all done if cb == 0

         mov        bx,seg_pbDest
         lar        ax,bx                    ; make sure we have access
         jnz        lcopyDone            ; no access
         les        di,pbDest

         lsl        ax,bx                    ; get the segment limit
         mov        bx,di                    ; check range
         add        bx,cx                    ; ending byte to copy
         jc        lcopyDone            ; overflowed
         cmp        ax,bx
         jc        lcopyDone            ; no room at destination

         mov        bx,seg_pbSrc
         lar        ax,bx                    ; make sure we have access
         jnz        lcopyDone            ; no access

         lds        si,pbSrc
         lsl        ax,bx                    ; get the segment limit
         mov        bx,si                    ; check range
         add        bx,cx                    ; ending byte to copy
         jc        lcopyDone            ; overflowed
         cmp        ax,bx
         jc        lcopyDone            ; Source is not big enough

         cmp        si,di
         jae        lcopyok
         mov        ax,cx
         dec        ax
         add        si,ax
         add        di,ax
         std
         rep        movsb
         cld
         jmp        short lcopydone
 lcopyok:
         cld
         rep        movsb
 lcopydone:
 cEnd




 cProc        BuildStackTrace,<NEAR,PUBLIC>,<SI,DI>
 ParmD        pStackSave
 ParmW        cCallsIgnore
 ParmW        cCallsSave

 cBegin
         les        di,pStackSave                ; where to save away stack in
         mov        bx,bp
         mov        dx,ss
         lsl        dx,dx
 ;
 ; First we need to ignore the defined number of entries
         mov        cx,cCallsIgnore
         jcxz        SaveLoop                ; Dont ignore any?
 NextBPToIgnore:
         and        bx,0FFFEh                ; Flush any INC BP bit
         jz        SaveLoop                ; End of chain if zero
         cmp        bx,dx                        ; BP outside of stack segment
         jae        SaveLoop                ; Yes, end of chain
         mov        si,bx                        ; Save current BP
         mov        bx,ss:[bx].savedBP        ; SS:BX -> next frame in BP chai
         and        bl,0FEh                 ; Flush any INC BP bit
         cmp        bx,si                        ; savedBP valid?
         jbe        SaveLoop                ; No, end of chain
         dec        cx                        ; decrement count of calls to ig
         jnz        NextBPToIgnore                ; Process the next one

 ;
 ; This loop will save away the specified number of calls into the
 ; passed in save area

 SaveLoop:
         mov        cx,cCallsSave                ; get count of calls to save

 NextBPToSave:
         and        bx,0FFFEh                ; Flush any INC BP bit
         jz        EndBPChain                    ; End of chain if zero
         cmp        bx,dx                        ; BP outside of stack segment
         jae        EndBPChain                    ; Yes, end of chain
 ; see if short or long call and setup selLast
         mov        si,ss:[bx].savedCS        ; Get the code segment
         lar        ax,si                        ; get the access rights
         jnz        CSNotValid                ; No access assume not selector
         and        ah,018h                 ; see if code segment
         cmp        ah,018h                 ;
         jz        CSValid                 ; code segment, so  save it

 CSNotValid:
         xor        si,si                        ; Not valid, set selector to
 CSValid:
         mov        ax,ss:[bx].savedIP        ; save away IP of caller
         stosw
         mov        ax,si                        ; and save away the code sele
         stosw
         dec        cx                        ; decrement count of how many to

         mov        si,bx                        ; Save current BP
         mov        bx,ss:[bx].savedBP        ; SS:BX -> next frame in BP chai
         and        bl,0FEh                 ; Flush any INC BP bit
         cmp        bx,si                        ; savedBP valid?
         jbe        EndBPChain                ; No
         jcxz        StackOverflowsSave        ; Stack is deeper than save are

         jmp        NextBPToSave
 ;
 ; We reached the end of the BP chain before we saved the specified number
 ; of stack items.  zero out the next item to signal caller how many items
 ; were filled in

 EndBPChain:
         jcxz        FillInSelectors
         xor        ax,ax                        ; Ended BP Chain Before count
         stosw                                ; Put A 0:0 to signal end
         stosw
         sub        di,4                        ; realign DI to last saved cal
         mov        ax,es:[di-2]                ; ax = Selector of last saved
         jmp        FillInSelectors         ;

 ;
 ; We saved the specified number of items, before we reached the end of the
 ; BP chain.  If the last item we saved was a local call, we should continue
 ; to go through the stack until we find a valid far return address.

 StackOverflowsSave:
         or        ax,ax                        ; Was last call local?
         jnz        FillInSelectors         ;

 ; loop to locate a FAR return address
 NextBPToCheck:
         and        bx,0FFFEh                ; Flush any INC BP bit
         jz        FarReturnNotFound        ; End of chain if zero
         cmp        bx,dx                        ; BP outside of stack segment
         jae        FarReturnNotFound        ; Yes, end of chain
 ; see if short or long call and setup selLast
         mov        ax,ss:[bx].savedCS        ; Get the code segment
         lar        cx,ax                        ; get the access rights
         jnz        CSNotValid2                ; No access assume not selector
         and        ch,018h                 ; see if code segment
         cmp        ch,018h                 ;
         jz        FillInSelectors         ; ax has valid code selector, use i

 CSNotValid2:
         mov        si,bx                        ; Save current BP
         mov        bx,ss:[bx].savedBP        ; SS:BX -> next frame in BP chai
         and        bl,0FEh                 ; Flush any INC BP bit
         cmp        bx,si                        ; savedBP valid?
         jg        NextBPToCheck
 FarReturnNotFound:
         mov        ax,0ffffh                ; use special value to show

 ;
 ; Loop through and convert all Short return addresses into long return
 ; addresses
 ;
 FillInSelectors:
         mov        cx,off_pStackSave
 FillInLoop:
         cmp        di,cx                        ; have we back tracked all th
         jz        FillInEnd                ; Yes
         sub        di,4                        ; point back to previous item
         cmp        WORD PTR es:[di+2],0        ; is the selector zero?
         jz        LocalReturn                ; Yes, local return
         mov        ax,es:[di+2]                ; No FAR return, save CS selec
         jmp        FillInLoop
 LocalReturn:
         mov        es:[di+2],ax                ; Make long return using saved
         jmp        FillInLoop

 FillInEnd:
 cEnd

 sEnd        CODE

 end        LoadProc


 SPYTBLS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SPYTBLS.C

 /***************************************************************************\
 * spytbls.c
 *
 *    Includes the definitions of the different tables used in the spy
 *    program
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

        INCL_WINHEAP                /* for spy.h */
        INCL_WINPOINTERS        /* for spy.h */
 #include <os2.h>
 #include "spyhook.h"
 #include <stdio.h>
 #include <string.h>
 #include "spy.h"


 /*
  * First define the message information table.  This table will be
  * used to display messages with
  */
 MSGI rgmsgi[] =
 {
     {0x0000, "WM_NULL", MSGI_ENABLED, 0, 0},
         {0x0001, "WM_CREATE", MSGI_ENABLED, 0, 0},
         {0x0002, "WM_DESTROY", MSGI_ENABLED, 0, 0},
         {0x0003, "WM_OTHERWINDOWDESTROYED", MSGI_ENABLED, 0, 0},
         {0x0004, "WM_ENABLE", MSGI_ENABLED, 0, 0},
         {0x0005, "WM_SHOW", MSGI_ENABLED, 0, 0},
         {0x0006, "WM_MOVE", MSGI_ENABLED, 0, 0},
         {0x0007, "WM_SIZE", MSGI_ENABLED, 0, 0},
         {0x0008, "WM_ADJUSTWINDOWPOS", MSGI_ENABLED, MPTS(MPT_SWP, 0), 0},
         {0x0009, "WM_CALCVALIDRECTS", MSGI_ENABLED, MPTS(MPT_RECTL, MPT_SWP),
         {0x000a, "WM_SETWINDOWPARAMS", MSGI_ENABLED, MPTS(MPT_WNDPRMS, 0), 0}
         {0x000b, "WM_QUERYWINDOWPARAMS", MSGI_ENABLED, 0, 0},
         {0x000c, "WM_HITTEST", MSGI_FREQ, 0, 0},
         {0x000d, "WM_ACTIVATE", MSGI_ENABLED, 0, 0},
         {0x000f, "WM_SETFOCUS", MSGI_ENABLED, 0, 0},
         {0x0010, "WM_SETSELECTION", MSGI_ENABLED, 0, 0},
         {0x0020, "WM_COMMAND", MSGI_ENABLED, 0, 0},
         {0x0021, "WM_SYSCOMMAND", MSGI_ENABLED, 0, 0},
         {0x0022, "WM_HELP", MSGI_ENABLED, 0, 0},
         {0x0023, "WM_PAINT", MSGI_ENABLED, 0, 0},
         {0x0024, "WM_TIMER", MSGI_FREQ, 0, 0},
         {0x0025, "WM_SEM1", MSGI_ENABLED, 0, 0},
         {0x0026, "WM_SEM2", MSGI_ENABLED, 0, 0},
         {0x0027, "WM_SEM3", MSGI_ENABLED, 0, 0},
         {0x0028, "WM_SEM4", MSGI_ENABLED, 0, 0},
         {0x0029, "WM_CLOSE", MSGI_ENABLED, 0, 0},
         {0x002a, "WM_QUIT", MSGI_ENABLED, 0, 0},
         {0x002b, "WM_SYSCOLORCHANGE", MSGI_ENABLED, 0, 0},
         {0x002d, "WM_SYSVALUECHANGED", MSGI_ENABLED, 0, 0},
         {0x0030, "WM_CONTROL", MSGI_ENABLED, 0, 0},
         {0x0031, "WM_VSCROLL", MSGI_ENABLED, 0, 0},
         {0x0032, "WM_HSCROLL", MSGI_ENABLED, 0, 0},
         {0x0033, "WM_INITMENU", MSGI_ENABLED, 0, 0},
         {0x0034, "WM_MENUSELECT", MSGI_ENABLED, 0, 0},
         {0x0035, "WM_MENUEND", MSGI_ENABLED, 0, 0},
         {0x0036, "WM_DRAWITEM", MSGI_ENABLED, 0, 0},
         {0x0037, "WM_MEASUREITEM", MSGI_ENABLED, 0, 0},
         {0x0038, "WM_CONTROLPOINTER", MSGI_FREQ, 0, 0},
         {0x0039, "WM_CONTROLHEAP", MSGI_ENABLED, 0, 0},
         {0x003a, "WM_QUERYDLGCODE", MSGI_ENABLED, 0, 0},
         {0x003b, "WM_INITDLG", MSGI_ENABLED, 0, 0},
         {0x003c, "WM_SUBSTITUTESTRING", MSGI_ENABLED, 0, 0},
         {0x003d, "WM_MATCHMNEMONIC", MSGI_ENABLED, 0, 0},
         {0x0040, "WM_FLASHWINDOW", MSGI_ENABLED, 0, 0},
         {0x0041, "WM_FORMATFRAME", MSGI_ENABLED, 0, 0},
         {0x0042, "WM_UPDATEFRAME", MSGI_ENABLED, 0, 0},
         {0x0043, "WM_FOCUSCHANGE", MSGI_ENABLED, 0, 0},
         {0x0044, "WM_SETBORDERSIZE", MSGI_ENABLED, 0, 0},
         {0x0045, "WM_TRACKFRAME", MSGI_ENABLED, 0, 0},
         {0x0046, "WM_MINMAXFRAME", MSGI_ENABLED, MPTS(MPT_SWP, 0), 0},
         {0x0047, "WM_SETICON", MSGI_ENABLED, 0, 0},
         {0x0048, "WM_QUERYICON", MSGI_ENABLED, 0, 0},
         {0x0049, "WM_SETACCELTABLE", MSGI_ENABLED, 0, 0},
         {0x004a, "WM_QUERYACCELTABLE", MSGI_ENABLED, 0, 0},
         {0x004b, "WM_TRANSLATEACCEL", MSGI_ENABLED, MPTS(MPT_QMSG, 0), 0},
         {0x004c, "WM_QUERYTRACKINFO", MSGI_ENABLED, 0, 0},
         {0x004d, "WM_QUERYBORDERSIZE", MSGI_ENABLED, 0, 0},
         {0x004e, "WM_NEXTMENU", MSGI_ENABLED, 0, 0},
         {0x004f, "WM_ERASEBACKGROUND", MSGI_ENABLED, MPTS(MPT_RECTL, 0), 0},
         {0x0050, "WM_QUERYFRAMEINFO", MSGI_ENABLED, 0, 0},
         {0x0051, "WM_QUERYFOCUSCHAIN", MSGI_ENABLED, MPTS(MPT_SWP, 0), 0},
         {0x0053, "WM_CALCFRAMERECT", MSGI_ENABLED, MPTS(MPT_RECTL, 0), 0},
         {0x0059, "WM_QUERYFRAMECTLCOUNT", MSGI_ENABLED, 0, 0},
         {0x0060, "WM_RENDERFMT", MSGI_ENABLED, 0, 0},
         {0x0061, "WM_RENDERALLFMTS", MSGI_ENABLED, 0, 0},
         {0x0062, "WM_DESTROYCLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0063, "WM_PAINTCLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0064, "WM_SIZECLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0065, "WM_HSCROLLCLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0066, "WM_VSCROLLCLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0067, "WM_DRAWCLIPBOARD", MSGI_ENABLED, 0, 0},
         {0x0070, "WM_MOUSEMOVE", MSGI_FREQ| MSGI_MOUSE, 0, 0},
         {0x0071, "WM_BUTTON1DOWN", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0072, "WM_BUTTON1UP", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0073, "WM_BUTTON1DBLCLK", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0074, "WM_BUTTON2DOWN", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0075, "WM_BUTTON2UP", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0076, "WM_BUTTON2DBLCLK", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0077, "WM_BUTTON3DOWN", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0078, "WM_BUTTON3UP", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x0079, "WM_BUTTON3DBLCLK", MSGI_ENABLED| MSGI_MOUSE, 0, 0},
         {0x007a, "WM_CHAR", MSGI_ENABLED | MSGI_KEY, 0, 0},
         {0x00A0, "WM_DDE_INITIATE", MSGI_ENABLED, 0, 0},
         {0x00A1, "WM_DDE_REQUEST", MSGI_ENABLED, 0, 0},
         {0x00A2, "WM_DDE_ACK", MSGI_ENABLED, 0, 0},
         {0x00A3, "WM_DDE_DATA", MSGI_ENABLED, 0, 0},
         {0x00A4, "WM_DDE_ADVISE", MSGI_ENABLED, 0, 0},
         {0x00A5, "WM_DDE_UNADVISE", MSGI_ENABLED, 0, 0},
         {0x00A6, "WM_DDE_POKE", MSGI_ENABLED, 0, 0},
         {0x00A7, "WM_DDE_EXECUTE", MSGI_ENABLED, 0, 0},
         {0x00A8, "WM_DDE_TERMINATE", MSGI_ENABLED, 0, 0},
         {0x00A9, "WM_DDE_INITIATEACK", MSGI_ENABLED, 0, 0},
         {0x00af, "WM_DDE_LAST", MSGI_ENABLED, 0, 0},
         {0x00b0, "WM_QUERYCONVERTPOS", MSGI_ENABLED, 0, 0},
         {0x0120, "BM_CLICK", MSGI_ENABLED, 0, 0},
         {0x0121, "BM_QUERYCHECKINDEX", MSGI_ENABLED, 0, 0},
         {0x0122, "BM_QUERYHILITE", MSGI_ENABLED, 0, 0},
         {0x0123, "BM_SETHILITE", MSGI_ENABLED, 0, 0},
         {0x0124, "BM_QUERYCHECK", MSGI_ENABLED, 0, 0},
         {0x0125, "BM_SETCHECK", MSGI_ENABLED, 0, 0},
         {0x0126, "BM_SETDEFAULT", MSGI_ENABLED, 0, 0},
         {0x0140, "EM_QUERYCHANGED", MSGI_ENABLED, 0, 0},
         {0x0141, "EM_QUERYSEL", MSGI_ENABLED, 0, 0},
         {0x0142, "EM_SETSEL", MSGI_ENABLED, 0, 0},
         {0x0143, "EM_SETTEXTLIMIT", MSGI_ENABLED, 0, 0},
         {0x0144, "EM_CUT", MSGI_ENABLED, 0, 0},
         {0x0145, "EM_COPY", MSGI_ENABLED, 0, 0},
         {0x0146, "EM_CLEAR", MSGI_ENABLED, 0, 0},
         {0x0147, "EM_PASTE", MSGI_ENABLED, 0, 0},
         {0x0148, "EM_QUERYFIRSTCHAR", MSGI_ENABLED, 0, 0},
         {0x0149, "EM_SETFIRSTCHAR", MSGI_ENABLED, 0, 0},
         {0x0160, "LM_QUERYITEMCOUNT", MSGI_ENABLED, 0, 0},
         {0x0161, "LM_INSERTITEM", MSGI_ENABLED, 0, 0},
         {0x0162, "LM_SETTOPINDEX", MSGI_ENABLED, 0, 0},
         {0x0163, "LM_DELETEITEM", MSGI_ENABLED, 0, 0},
         {0x0164, "LM_SELECTITEM", MSGI_ENABLED, 0, 0},
         {0x0165, "LM_QUERYSELECTION", MSGI_ENABLED, 0, 0},
         {0x0166, "LM_SETITEMTEXT", MSGI_ENABLED, 0, 0},
         {0x0167, "LM_QUERYITEMTEXTLENGTH", MSGI_ENABLED, 0, 0},
         {0x0168, "LM_QUERYITEMTEXT", MSGI_ENABLED, 0, 0},
         {0x0169, "LM_SETITEMHANDLE", MSGI_ENABLED, 0, 0},
         {0x016a, "LM_QUERYITEMHANDLE", MSGI_ENABLED, 0, 0},
         {0x016b, "LM_SEARCHSTRING", MSGI_ENABLED, 0, 0},
         {0x016c, "LM_SETITEMHEIGHT", MSGI_ENABLED, 0, 0},
         {0x016d, "LM_QUERYTOPINDEX", MSGI_ENABLED, 0, 0},
         {0x016e, "LM_DELETEALL", MSGI_ENABLED, 0, 0},
         {0x0180, "MM_INSERTITEM", MSGI_ENABLED, 0, 0},
         {0x0181, "MM_DELETEITEM", MSGI_ENABLED, 0, 0},
         {0x0182, "MM_QUERYITEM", MSGI_ENABLED, 0, 0},
         {0x0183, "MM_SETITEM", MSGI_ENABLED, 0, 0},
         {0x0184, "MM_QUERYITEMCOUNT", MSGI_ENABLED, 0, 0},
         {0x0185, "MM_STARTMENUMODE", MSGI_ENABLED, 0, 0},
         {0x0186, "MM_ENDMENUMODE", MSGI_ENABLED, 0, 0},
         {0x0187, "MM_DISMISSMENU", MSGI_ENABLED, 0, 0},
         {0x0188, "MM_REMOVEITEM", MSGI_ENABLED, 0, 0},
         {0x0189, "MM_SELECTITEM", MSGI_ENABLED, 0, 0},
         {0x018a, "MM_QUERYSELITEMID", MSGI_ENABLED, 0, 0},
         {0x018b, "MM_QUERYITEMTEXT", MSGI_ENABLED, 0, 0},
         {0x018c, "MM_QUERYITEMTEXTLENGTH", MSGI_ENABLED, 0, 0},
         {0x018d, "MM_SETITEMHANDLE", MSGI_ENABLED, 0, 0},
         {0x018e, "MM_SETITEMTEXT", MSGI_ENABLED, 0, 0},
         {0x018f, "MM_ITEMPOSITIONFROMID", MSGI_ENABLED, 0, 0},
         {0x0190, "MM_ITEMIDFROMPOSITION", MSGI_ENABLED, 0, 0},
         {0x0191, "MM_QUERYITEMATTR", MSGI_ENABLED, 0, 0},
         {0x0192, "MM_SETITEMATTR", MSGI_ENABLED, 0, 0},
         {0x0193, "MM_ISITEMVALID", MSGI_ENABLED, 0, 0},
         {0x01a0, "SBM_SETSCROLLBAR", MSGI_ENABLED, 0, 0},
         {0x01a1, "SBM_SETPOS", MSGI_ENABLED, 0, 0},
         {0x01a2, "SBM_QUERYPOS", MSGI_ENABLED, 0, 0},
         {0x01a3, "SBM_QUERYRANGE", MSGI_ENABLED, 0, 0},
         {0x01a4, "SBM_SETHILITE", MSGI_ENABLED, 0, 0},
         {0x01a5, "SBM_QUERYHILITE", MSGI_ENABLED, 0, 0},
         {0x01e3, "TBM_SETHILITE", MSGI_ENABLED, 0, 0},
         {0x01e4, "TBM_QUERYHILITE", MSGI_ENABLED, 0, 0},
 };

 SHORT cmsgi = sizeof(rgmsgi) / sizeof(MSGI);


 /*
  * Now define the table of system known window ids, that is
  * used to display the list of windows with
  */
 /* Define table of window ids */
 IDTONAME    rgidtoname[] = {
         {0x8001, "FID_SIZEBORDER"},
         {0x8002, "FID_SYSMENU"},
         {0x8003, "FID_TITLEBAR"},
         {0x8004, "FID_MINMAX"},
         {0x8005, "FID_MENU"},
         {0x8006, "FID_VERTSCROLL"},
         {0x8007, "FID_HORZSCROLL"},
         {0x8008, "FID_CLIENT"}
 };

 SHORT cToName =  sizeof(rgidtoname) / sizeof(IDTONAME);


 /*
  * And define the system options
  */

 SPYOPT spyopt = {
     /* Output options */
     TRUE, FALSE, 100, NULL, SPYH_INPUT | SPYH_SENDMSG,
         TRUE, TRUE,
         FALSE,
         TRUE, FALSE

 };

 SPYSTR spystr = {
     "spy.out",      /* Default output file */
     "spy.lis"       /* Default save to list file */
 };


 SPYUTIL.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SPYUTIL.ASM

 ;  SPY Assembly language utility functions.

 ?WIN=1            ; Use Windows prolog/epilog
 ?PLM=1            ; Use PLM calling convention
 DOS5=1
 .xlist
 include cmacros.inc
 .list



 EXTRN        DOSCHGFILEPTR:FAR
 EXTRN        DOSREAD:FAR


 sBegin        CODE
 assumes cs,CODE
 assumes ds,DATA
 .286p

 ; FValidPointer
 ;
 ; FValidPointer(VOID FAR *pVoid, SHORT cbStruct)
 ;
 cProc        FValidPointer,<NEAR,PUBLIC>
 ParmD  pVoid
 ParmW        cbStruct
 cBegin
         xor        ax,ax                        ; Assume Bad address
         mov        bx,SEG_pVoid                ; Get the selector of the addr
         lar        cx,bx                        ; make sure we have access
         jnz        NoAccess                ; no access

         lsl        cx,bx                        ; get the segment limit
         mov        bx,OFF_pVoid                ; check range
         add        bx,cbStruct                ; Add number of bytes on
         jc        NoAccess                ; overflowed
         cmp        cx,bx
         jc        NoAccess                ; Would overflow, again bad pointer
         inc        ax                        ; Return No zero for TRUE

 NoAccess:
 cEnd



 ; FGuessValidPointer
 ;
 ; FGuessValidPointer(VOID FAR *pVoid, SHORT cbStruct)
 ; Since a pointer may have come from a different process, we can not
 ; actually validate the pointer.  The Best we can do is simply look
 ; at the selector, and see if it looks reasonable.  For now simply check
 ; that the selector is for ring 3.  Ie the low order two bits are set.
 ;
 cProc        FGuessValidPointer,<NEAR,PUBLIC>
 ParmD  pVoid
 ParmW        cbStruct
 cBegin
         xor        ax,ax                        ; Assume Bad address
         mov        bx,SEG_pVoid                ; Get the selector of the addr
         and        bx,03h                        ; only look at two low order
         cmp        bx,03h                        ; Are they both set
         jnz        NoGVAccess                ; not ring 3 assume no access
         inc        ax                        ; Return No zero for TRUE

 NoGVAccess:
 cEnd



 cProc        DebugFileSeek,<PUBLIC,NEAR,PASCAL>
     parmW   fh
     parmD   amt
     parmW   typ
 cBegin
         push        ax
         push        ax
         mov        bx,sp
         push        fh
         mov        dx,SEG_amt
         mov        ax,OFF_amt
         mov        cx,typ
         cmp        cx,2
         jle        noshift
 shiftamt:
         shl        ax,1
         rcl        dx,1
         loop        shiftamt
 noshift:
         push        dx
         push        ax
         push        cx
         push        ss
         push        bx
         call        DOSCHGFILEPTR
         or        ax,ax
         pop        ax
         pop        dx
         jz        seek_done
         xor        ax,ax
         xor        dx,dx
 seek_done:
 cEnd



 cProc        DebugFileRead,<PUBLIC,NEAR,PASCAL>
     parmW   fh
     parmD   lpBuf
     parmW   nBytes
 cBegin
         push        ax
         mov        ax,sp
         push        fh
         push        SEG_lpBuf
         push        OFF_lpBuf
         push        nBytes
         push        ss
         push        ax
         call        DOSREAD
         or        ax,ax
         pop        ax
         jz        read_done
         xor        ax,ax
 read_done:
 cEnd

 cProc lstrcat,<PUBLIC>,<SI,DI,DS>
     parmD  szDest
     parmD  szSource

 cBegin        lstrcat

 ; first lets get the length of the source string and setup pointer to
 ; source string

         les        di,szSource
         mov        cx,-1                ; count the bytes negatively
         xor        ax,ax                ; Look for a null

         repne scasb                ; find null byte & get source length
         inc        cx                ; cx=-count of bytes in source string
                                 ;    (including null)
         neg        cx                ; cx=+count
         mov        bx,cx                ; save the count

         lds        si,szSource        ; now setup Source pointer

 ; now find the end of destination string.
         les        di,szDest
         xor        ax,ax
         mov        cx,0ffffH
         repne scasb
         dec        di                ; di points to dest null terminator

         mov        cx,bx                ; restore count of bytes to copy

         rep movsb                ; concatenate the strings

 cEnd        lstrcat

 sEnd        CODE
 end


 STOCK.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\STOCK\STOCK.C

 /***        stock - draw a stock chart
  *
  *        Author:
  *            Benjamin W. Slivka
  *
  *        History:
  *            27-Mar-1989 bens        Initial version.
  *            06-Apr-1989 bens        Always repaint on WM_SIZE.
  *            26-Apr-1990 bens        Get it limping along
  *            03-May-1990 bens        Add file open
  *            11-May-1990 bens        Printing works!
  *            06-Jun-1990 bens        Finish job property handling
  *            07-Jun-1990 bens        Fix bugs in JP stuff, spiff up user int
  */

 #define INCL_DEV

 #define INCL_GPIPRIMITIVES

 #define INCL_WINDIALOGS
 #define INCL_WINERRORS
 #define INCL_WINFRAMEMGR
 #define INCL_WINLISTBOXES
 #define INCL_WINMENUS
 #define INCL_WINPOINTERS
 #define INCL_WINWINDOWMGR

 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #include "ea.h"
 #include "ids.h"
 #include "file.h"
 #include "limits.h"
 #include "mem.h"
 #include "print.h"


 #define cchDate    10
 #define cchMax           80

 #define iH            0            // index of High
 #define iL            1            // index of Low
 #define iC            2            // index of Close
 #define NV            3            // Number of vectors
 #define cptMax         1200            // maximum data points


 #define ENABLE_MENU_ITEM(x, y)                                    \
         WinSendDlgItemMsg((x), FID_MENU, MM_SETITEMATTR,    \
             MPFROM2SHORT((y),TRUE),                            \
             MPFROM2SHORT(MIA_DISABLED,NULL))

 #define DISABLE_MENU_ITEM(x, y)                             \
         WinSendDlgItemMsg((x), FID_MENU, MM_SETITEMATTR,    \
             MPFROM2SHORT((y),TRUE),                            \
             MPFROM2SHORT(MIA_DISABLED,MIA_DISABLED))

 #define CHECK_MENU_ITEM(x, y)                                    \
         WinSendDlgItemMsg((x), FID_MENU, MM_SETITEMATTR,    \
             MPFROM2SHORT((y),TRUE),                            \
             MPFROM2SHORT(MIA_CHECKED,MIA_CHECKED))

 #define UNCHECK_MENU_ITEM(x, y)                             \
         WinSendDlgItemMsg((x), FID_MENU, MM_SETITEMATTR,    \
             MPFROM2SHORT((y),TRUE),                            \
             MPFROM2SHORT(MIA_CHECKED,NULL))


 MRESULT EXPENTRY ClientWndProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2);
 MRESULT EXPENTRY AboutDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2);
 MRESULT EXPENTRY PrinterSetupDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM m


 HPRINTER    HPRTFromListBox(HWND hwnd);
 BOOL            InteractSaveFile(HWND hwnd);
 int cdecl   main(int cArg,char **ppszArg);
 VOID            Message(char *psz, HWND hwnd);
 VOID            PlotPrice(HPS hps,RECTL rcl);
 BOOL            QueryHCI(HDC hdc,HCINFO *phci);
 BOOL            PrintChart(HWND hwnd);
 BOOL            ReadData(HWND hwnd);
 VOID            SaveJobProperties(VOID);
 VOID            SetOriginMenu(VOID);
 VOID            SetFileMenu(VOID);
 VOID            ShowWinError(char *psz);


 /***        GLOBAL - global data
  *
  */
 typedef struct {    /* glb */
     char         achAppTitle[CCHMAXPATH]; // Application title
     USHORT         cpt;                // Number of points
     BOOL         fNewJobProp;        // TRUE => job properties changed
     BOOL         fZeroOrigin;        // TRUE => Y origin is 0; else is minimu
     HAB          hab;
     HWND         hwndFrame;
     HWND         hwndClient;
     USHORT         pMax;                // Maximum price
     USHORT         pMin;                // Minimum price
     char *         pszFile;        // Data file
     HPRINTER         hprt;                // Current printer handle
     HPRINTERLIST hprtlist;        // Handle of printer list
 } GLOBAL;

 char            achMisc[CCHMAXPATH];
 USHORT            apt[NV][cptMax];
 GLOBAL            glb;
 HPOINTER    hptrNormal;
 HPOINTER    hptrWait;
 char            szClientClass[]="Stock";
 char            pszJobProp[]="StockJobProp";


 /***        main - main program
  *
  */
 int cdecl main(int cArg, char **ppszArg)
 {
     ULONG            flFrameFlags= FCF_TITLEBAR            | FCF_SYSMENU  |
                                   FCF_SIZEBORDER    | FCF_MINMAX   |
                                   FCF_SHELLPOSITION | FCF_TASKLIST |
                                   FCF_MENU;
     HMQ             hmq;
     QMSG            qmsg;

     glb.hprt = NULL;
     glb.cpt = 0;
     glb.fZeroOrigin = TRUE;            // Use a zero origin
     glb.fNewJobProp = FALSE;            // No new job properties

     // If at least one argument, take that as data file name
     if (cArg >= 2)
         glb.pszFile = MemStrDup(ppszArg[1]);
     else
         glb.pszFile = NULL;

     glb.hab = WinInitialize(0);
     hmq = WinCreateMsgQueue(glb.hab,0);

     hptrWait = WinQuerySysPointer(HWND_DESKTOP,SPTR_WAIT,FALSE);
     hptrNormal = WinQuerySysPointer(HWND_DESKTOP,SPTR_ARROW,FALSE);

     WinRegisterClass(glb.hab,szClientClass,ClientWndProc,0L,0);

     glb.hwndFrame = WinCreateStdWindow(HWND_DESKTOP,
                         WS_VISIBLE | FS_ICON | FS_TASKLIST, // Window styles
                         &flFrameFlags,
                         szClientClass,
                         NULL,
                         0L,
                         (HMODULE) NULL,
                         IDR_APP,
                         &glb.hwndClient);

     if (glb.hwndFrame == 0) {
         Message("Cannot create main window",NULL);
         WinDestroyMsgQueue(hmq);
         WinTerminate(glb.hab);
         return 1;
     }

     // Get printer list
     glb.hprtlist = PrintCreatePrinterList(glb.hab);

     // Get application title
     WinQueryWindowText(glb.hwndFrame, sizeof(glb.achAppTitle),glb.achAppTitle

     if (glb.pszFile != NULL)
         ReadData(glb.hwndClient);

     // Get printer, if not already set by ReadData

     if (glb.hprt == NULL) {                // Need to find it
         if (glb.hprtlist != NULL) {        // Printers exists
             glb.hprt = PrintQueryDefaultPrinter(glb.hprtlist); // Get default
         }
     }

     // Set up valid menu options

     SetFileMenu();
     SetOriginMenu();

     // Message loop

     while (WinGetMsg(glb.hab,&qmsg,NULL,0,0)) {
         WinDispatchMsg(glb.hab,&qmsg);
     }

     // Destroy printer list

     if (glb.hprtlist != NULL)
         PrintDestroyPrinterList(glb.hprtlist);

     WinDestroyWindow(glb.hwndFrame);
     WinDestroyMsgQueue(hmq);
     WinTerminate(glb.hab);
     return 0;
 }


 /***        ClientWndProc - window proc for client window
  *
  */
 MRESULT EXPENTRY ClientWndProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2)
 {
     HPS     hps;
     char *  psz;
     RECTL   rcl;

     switch (msg) {

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {

         case IDM_ABOUT:
             WinDlgBox(HWND_DESKTOP,hwnd,AboutDlgProc,NULL,IDD_ABOUT,NULL);
             return FALSE;

         case IDM_FILE_CLOSE:
             // Make sure current file is saved
             if (!InteractSaveFile(hwnd)) // User cancelled
                 return FALSE;

             // Empty data

             glb.cpt = 0;
             if (glb.pszFile != NULL) {
                 MemFree(glb.pszFile);
                 glb.pszFile = NULL;
             }
             WinInvalidateRect(hwnd,NULL,FALSE); // Force repaint


             SetFileMenu();                // Set valid menu options
             return FALSE;

         case IDM_FILE_OPEN:
             // Make sure current file is saved
             if (!InteractSaveFile(hwnd)) // User cancelled
                     return FALSE;

             psz = FileOpen(hwnd, "Open", "*.dat");
             if (psz != NULL) {
                 if (glb.pszFile != NULL) // Old file data present
                     MemFree(glb.pszFile); // Free old file name
                 glb.pszFile = psz;        // Set new file name
                 if (ReadData(hwnd))        // If data was read successfully
                     WinInvalidateRect(hwnd,NULL,FALSE); // Force repaint
             }
             return FALSE;

         case IDM_FILE_PRINT:

             PrintChart(hwnd);
             break;

         case IDM_FILE_PRINTER_SETUP:
             WinDlgBox(HWND_DESKTOP,hwnd,PrinterSetupDlgProc,NULL,
                                                  IDD_PRINTER_SETUP,NULL);
             return FALSE;

         case IDM_FILE_SAVE:
             SaveJobProperties();
             SetFileMenu();
             return FALSE;

         case IDM_FILE_EXIT:
             WinSendMsg(hwnd,WM_QUIT,NULL,NULL);
             break;

         case IDM_OPTIONS_ORIGIN:
             glb.fZeroOrigin = !glb.fZeroOrigin; // Flip state
             SetOriginMenu();
             return FALSE;
         }
         break;

     case WM_CREATE:
         break;

     case WM_QUIT:
         if (!InteractSaveFile(hwnd))            // User cancelled
             return FALSE;                    //        Do not quit
         break;                                    // Do standard quit process

     case WM_SIZE:
         WinInvalidateRect(hwnd,NULL,FALSE);
         return 0;

     case WM_PAINT:
         hps = WinBeginPaint(hwnd,NULL,NULL);
         GpiErase(hps);
         WinQueryWindowRect(hwnd,&rcl);
         PlotPrice(hps,rcl);
         WinEndPaint(hps);
         return 0;
     }
     return WinDefWindowProc(hwnd,msg,mp1,mp2);
 }


 /***        InteractSaveFile - If new job properties, let user save them
  *
  *        Entry
  *            hwnd - parent window for message box
  *            glb.fNewJobProp
  *
  *        Exit-Success
  *            returns TRUE, file is saved
  *
  *        Exit-Failure
  *            returns FALSE, user cancelled save
  */
 BOOL InteractSaveFile(HWND hwnd)
 {
     static char ach[CCHMAXPATH];
     USHORT        rc;

     if (!glb.fNewJobProp)                // No job properties to save
         return TRUE;

     sprintf(ach,"Save printer setup in '%s'?",glb.pszFile);
     rc = WinMessageBox(
             HWND_DESKTOP,                // Parent
             hwnd,                        // Owner
             ach,                        // Message
             szClientClass,                // Caption (use default)
             NULL,                        // Window id
             MB_QUERY | MB_YESNOCANCEL | MB_APPLMODAL // window style
     );
     switch (rc) {
         case MBID_YES:                        // Save job properties
             SaveJobProperties();
             SetFileMenu();
             return TRUE;

         case MBID_NO:                        // Do not save job properties
             glb.fNewJobProp = FALSE;
             SetFileMenu();
             return TRUE;

         case MBID_CANCEL:                // Cancel save
             return FALSE;
     }
     return FALSE;
 }


 /***        PrintChart - Draw chart on printer
  *
  *        Entry
  *            hwnd - hwnd for status
  *
  *            glb.hab  - Anchor block
  *            glb.hprt - Printer destination
  *            glb.pszFile - File name (for job description)
  *
  *            Same variables as PlotPrice;
  *
  *        Exit-Success
  *            returns TRUE
  *
  *        Exit-Failure
  *            returns FALSE
  */
 BOOL PrintChart(HWND hwnd)
 {
     LONG            cb;
     USHORT            jobid;
     HDC             hdc=NULL;
     HPS             hps=NULL;
     static HCINFO   hci;
     LONG            rc;
     RECTL            rcl;
     SIZEL            sizl;

     WinSetPointer(HWND_DESKTOP,hptrWait); // Tell user we are being slow

     // Create DC

     hdc = PrintOpenDC(glb.hab,glb.hprt,"PM_Q_STD");
     if (hdc == NULL) {
         ShowWinError("PrintOpenDC failed");
         rc = DEV_ERROR;
         goto Exit;
     }

     if (!QueryHCI(hdc,&hci)) {
         Message("QueryHCI failed",NULL);
         rc = DEV_ERROR;
         goto Exit;
     }

     // Create and associate PS
     sizl.cx = 0L;
     sizl.cy = 0L;
     hps = GpiCreatePS(glb.hab,hdc,&sizl,GPIA_ASSOC | PU_PELS | GPIT_NORMAL);
     if (hps == NULL) {
         ShowWinError("GpiCreatePS failed");
         rc = DEV_ERROR;
         goto Exit;
     }

     // Set dimensions of drawing area
     rcl.xLeft        = 0;
     rcl.yBottom = 0;
     rcl.xRight        = hci.xPels;
     rcl.yTop        = hci.yPels;

     // Time to draw
     rc = DevEscape(
             hdc,                        // Display context
             DEVESC_STARTDOC,                // Start document
             (LONG)strlen(glb.pszFile),        // Length of print job name
             glb.pszFile,                // Print job name
             0L,                         // Ignored
             0L                                // Ignored
          );
     if (rc != DEV_OK) {
         ShowWinError("STARTDOC failed");
         goto Exit;
     }

     PlotPrice(hps,rcl);

     cb = sizeof(jobid);                 // Size of output buffer
     rc = DevEscape(
             hdc,                        // Display context
             DEVESC_ENDDOC,                // End document
             0L,                         // Length of print job name
             0L,                         // Print job name
             &cb,                        // Size of jobid
             (PBYTE)&jobid                // Jobid
          );
     if (rc != DEV_OK)
         ShowWinError("ENDDOC failed");

     // Free resources
 Exit:
     if (hps != NULL) {
         GpiAssociate(hps,NULL);
         GpiDestroyPS(hps);
     }
     if (hdc != NULL)
         DevCloseDC(hdc);
     WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
     return (rc == DEV_OK);
 }


 /***        ShowWinError - Show message box with error from Win* API call
  *
  *        Entry
  *            psz - message text
  *
  *        Exit
  *            none
  *
  */
 VOID ShowWinError(char *psz)
 {
     PERRINFO            perri;

     perri = WinGetErrorInfo(glb.hab);
     if (perri == (PERRINFO) NULL)
         return;

     sprintf(achMisc,"%s; Error = 0x%0x",psz,perri->idError);
     Message(achMisc,NULL);
     WinFreeErrorInfo(perri);
 }


 /***        SetOriginMenu - Set text in origin menu
  *
  *        Entry
  *            glb.fZeroOrgin - current state of origin plotting
  *
  *        Exit
  *            Menu item checked/unchecked
  */
 VOID SetOriginMenu(VOID)
 {
     if (glb.fZeroOrigin)
         UNCHECK_MENU_ITEM(glb.hwndFrame,IDM_OPTIONS_ORIGIN);
     else
         CHECK_MENU_ITEM(glb.hwndFrame,IDM_OPTIONS_ORIGIN);

     WinInvalidateRect(glb.hwndClient,NULL,FALSE); // Force repaint
 }


 /***        SetFileMenu - Set file menu item states
  *
  */
 VOID SetFileMenu(VOID)
 {
     static char ach[CCHMAXPATH];

     strcpy(ach,glb.achAppTitle);        // Title start with app name

     if (glb.pszFile == NULL) {                // No file currently open
         DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_CLOSE);
         DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_SAVE);
         DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINT);
         DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINTER_SETUP);
     }
     else {
         ENABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_CLOSE);

         if (glb.fNewJobProp)                // Job properties need to be save
             ENABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_SAVE);
         else
             DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_SAVE);

         if (glb.hprt != NULL) {         // A printer exists
             ENABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINT);
             ENABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINTER_SETUP);
         }
         else {
             DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINT);
             DISABLE_MENU_ITEM(glb.hwndFrame,IDM_FILE_PRINTER_SETUP);
         }

         strcat(ach," - ");                // Append file name
         strcat(ach,glb.pszFile);
     }
     WinSetWindowText(glb.hwndFrame,ach); // Set title
 }


 /***        ReadData - read stock data from file
  *
  *        Entry
  *            hwnd         - Hwnd for status messages
  *            glb.pszFile  - File to read
  *            glb.hprtlist - Printer list
  *
  *        Exit-Success
  *            Returns TRUE
  *                apt[][]  = Filled in with price data
  *                glb.cpt  = Count of points read
  *                glb.pMax = Maximum price
  *                glb.pMin = Minimum price
  *                glb.hprt = May have change, to file job properties
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 BOOL ReadData(HWND hwnd)
 {
     static char   ach[CCHMAXPATH];
     static USHORT cb;
     int           cf;
     USHORT          factorSplit;
     USHORT          fracClose;
     USHORT          fracHigh;
     USHORT          fracLow;
     HPRINTER          hprt;
     int           i;
     USHORT          intClose;
     USHORT          intHigh;
     USHORT          intLow;
     int           j;
     char *          pb;
     char *          pch;
     char *          pch1;
     FILE *          pfile;
     ULONG          ulVol;

     WinSetPointer(HWND_DESKTOP,hptrWait); // Tell user we are being slow

     glb.cpt = 0;
     glb.fNewJobProp = FALSE;                // Job properties not changed
     SetFileMenu();

     pfile = fopen(glb.pszFile,"r");
     if (pfile == NULL) {
         WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
         sprintf(achMisc,"Could not open \"%s\"",glb.pszFile);
         Message(achMisc,hwnd);
         return FALSE;
     }

     fgets(ach,cchMax,pfile);   // skip two header lines
     fgets(ach,cchMax,pfile);
     fgets(ach,cchMax,pfile);   // Get first data line
     while (!feof(pfile)) {
         if (glb.cpt > cptMax) {
             WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
             sprintf(achMisc,"Too many data points. Max is %d.",cptMax);
             Message(achMisc,hwnd);
 // BUGBUG 03-May-1990 bens Should continue with truncated data?
 //
 // Should make file read a separate function, and do min/max computation
 // as long as some data was read.
 //
         return FALSE;
         }

         // Skip date
         pch = strchr(ach,'"')+1;        // pch        -> 04/24/60",....
         pch1 = strchr(pch,'"');         // pch1 ->           ",....
         *pch1 = '\0';                        // Split string at end of date
         pch1 += 2;                        // Skip ",

         cf = sscanf(pch1," %d.%d, %d.%d, %d.%d, %ld",
               &intHigh,&fracHigh,&intLow,&fracLow,&intClose,&fracClose,&ulVol

         if (cf != 7) {
             WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
             // Adjust count for two header lines, plus base 0
             sprintf(achMisc,"Line %d is bad: [%s]",glb.cpt+3,ach);
             Message(achMisc,hwnd);
 // BUGBUG 03-May-1990 bens What to do now?
 //
 // On par with previous BUGBUG, we should probably display data up to the
 // point where it was bad.
 //
 // Also, it would be nice to give a hint about the problem.
 //
 // At a minimum, tell if too many or too few data points.
             return FALSE;
         }

         // convert to units of 8 points
         intHigh  = (intHigh  << 3) + (fracHigh        / 125);
         intLow         = (intLow   << 3) + (fracLow        / 125);
         intClose = (intClose << 3) + (fracClose / 125);

         if (stricmp(pch,"SPLIT") == 0) {
             factorSplit = intHigh/8;
             for (i=0; i<(int) glb.cpt; i++)            // Process all saved r
                 for (j=0; j<NV; j++)        // Process dollar values
                     apt[j][i] /= factorSplit;
                 // dP(iV, j%) *= factorSplit;
         }
         else {
             apt[iH][glb.cpt] = intHigh;
             apt[iL][glb.cpt] = intLow;
             apt[iC][glb.cpt] = intClose;
             glb.cpt++;
         }
         fgets(ach,cchMax,pfile);        // Get next data line
     }
     fclose(pfile);

     // Find max and min prices

     // Speed win: prime min/max with real value.
     //        This allows the if..else if below to work reliably.

     glb.pMax = apt[0][0];
     glb.pMin = apt[0][0];

     for (i=0; i<(int)glb.cpt; i++) {
         for (j=0; j<NV; j++) {
             if (apt[j][i] > glb.pMax)
                 glb.pMax = apt[j][i];
             else if (apt[j][i] < glb.pMin)
                 glb.pMin = apt[j][i];
         }
     }

     // Select printer, based on job properties

     pb = EAQueryValue(glb.pszFile,pszJobProp,&cb);
     if (pb == NULL) {                        // No job prop, use default prin
         WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
         return TRUE;                        //  Done
     }
     if (cb == 0) {                        // Data is not binary
         MemFree(pb);                        // Free buffer
         WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
         return TRUE;                        //  Done
     }

     // Try to match a printer

     hprt = PrintMatchPrinter(glb.hprtlist,cb,pb);
     if (hprt != NULL)                        // Got a match!
         glb.hprt = hprt;                // Set new printer

     MemFree(pb);                        // Free buffer

     SetFileMenu();                        // Update menus and title bar

     WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
     return TRUE;                        //  Done
 }


 /***        PlotPrice - plot stock price
  *
  *        Entry
  *            hps - Presentation space to plot to
  *            rcl - Bounding rectangle in hps
  *
  *            apt[][]  = Filled in with price data
  *            glb.cpt  = Count of points read
  *            glb.pMax = Maximum price
  *            glb.pMin = Minimum price
  *            glb.hprt = May have change, to file job properties
  *            glb.fZeroOrigin - TRUE => make Y-origin be 0.
  *
  *        Exit
  *            Stock chart plotted
  */
 VOID PlotPrice(HPS hps,RECTL rcl)
 {
     SHORT   cxBar;
     int     i;
     POINTL  ptl;
     SHORT   x;
     float   xFactor;
     SHORT   xMargin = 4;
     SHORT   yC;
     float   yFactor;
     SHORT   yH;
     SHORT   yL;
     SHORT   yMargin = 4;
     SHORT   yOrigin;

     if (glb.cpt == 0)                        // No points to draw
         return;

     if (glb.fZeroOrigin)
         yOrigin = 0;
     else
         yOrigin = glb.pMin;

     yFactor = (float)(rcl.yTop - rcl.yBottom - (LONG)(2*yMargin)) /
               (float)(glb.pMax-yOrigin);
     xFactor = (float)(rcl.xRight - rcl.xLeft - (LONG)(2*xMargin)) /
               (float)(glb.cpt);

     cxBar = (USHORT)(xFactor/2.0);
     if (cxBar < 1)
         cxBar = 1;

     WinSetPointer(HWND_DESKTOP,hptrWait); // Tell user we are being slow

     for (i=0; i<(int)glb.cpt; i++) {
             x = (USHORT)(i*(float)xFactor) + xMargin;
             yH = (USHORT)((float)(apt[iH][i]-yOrigin)*yFactor) + yMargin;
             yL = (USHORT)((float)(apt[iL][i]-yOrigin)*yFactor) + yMargin;
             yC = (USHORT)((float)(apt[iC][i]-yOrigin)*yFactor) + yMargin;

             // Draw vertical (low to high) bar

             GpiSetColor(hps,CLR_CYAN);

             ptl.x = x;
             ptl.y = yL;
             GpiMove(hps,&ptl);

             ptl.y = yH;
             GpiLine(hps,&ptl);

             // Draw horizontal (close) bar

             GpiSetColor(hps,CLR_RED);

             ptl.x = x;
             ptl.y = yC;
             GpiMove(hps,&ptl);

             ptl.x += cxBar;
             GpiLine(hps,&ptl);
     }
     WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
 }


 /***        AboutDlgProc - "About..." Dialog Procedure
 *
 */
 MRESULT EXPENTRY AboutDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM mp2)
 {
     switch (msg) {
         case WM_COMMAND:
             switch (SHORT1FROMMP(mp1)) {
                 case DID_OK:
                     WinDismissDlg(hwnd,TRUE);
                     return FALSE;
             }
             break;
     }
     return WinDefDlgProc(hwnd,msg,mp1,mp2);
 }


 /***        PrinterSetupDlgProc - "File Printer Setup..." Dialog Procedure
 *
 */
 MRESULT EXPENTRY PrinterSetupDlgProc(HWND hwnd,USHORT msg,MPARAM mp1,MPARAM m
 {
     static BOOL     fNewJobProp;
     HPRINTER            hprt;
     HWND            hwndLB;
     USHORT            i;
     char *            pszName;
     char *            pszNameDefault;

     switch (msg) {

     case WM_INITDLG:
         fNewJobProp = FALSE;
         hwndLB = WinWindowFromID(hwnd, IDL_PRINTER);
         hprt = 0;                        // Enumerate full list of printers
         while ((hprt=PrintQueryNextPrinter(glb.hprtlist,hprt)) != NULL) {
             pszName = (char *)PrintQueryPrinterInfo(hprt,PQPI_NAME);
             // Insert item and store handle
             i = SHORT1FROMMR(WinSendMsg(hwndLB, LM_INSERTITEM,
                            MPFROMSHORT(LIT_SORTASCENDING),MPFROMP(pszName)));
             WinSendMsg(hwndLB,LM_SETITEMHANDLE,
                                           MPFROMSHORT(i),MPFROMP(hprt));
             if (hprt == glb.hprt)        // This is the default printer
                 pszNameDefault = pszName; // Save name to set selection
         }

         // Find default printer and set selection

         i = SHORT1FROMMR(WinSendMsg(hwndLB,LM_SEARCHSTRING,
                          MPFROM2SHORT(0,LIT_FIRST),MPFROMP(pszNameDefault)));
         WinSendMsg(hwndLB,LM_SELECTITEM,MPFROMSHORT(i),MPFROMSHORT(TRUE));
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {

         case DID_OK:
             hprt = HPRTFromListBox(hwnd); // Get selection
             if (hprt != glb.hprt) {        // Printer destination changed
                 glb.hprt = hprt;        // Remember new printer
                 fNewJobProp = TRUE;        // Force new job properties
             }
             glb.fNewJobProp = fNewJobProp; // User made change
             SetFileMenu();
             WinDismissDlg(hwnd,TRUE);
             return FALSE;

         case IDB_SETUP:
             hprt = HPRTFromListBox(hwnd); // Get selection
             if (PrintChangeJobProperties(hprt)) // Properties changed
                 fNewJobProp = TRUE;        //  Remember JP changed
             return FALSE;

         case IDB_RESET:
             hprt = HPRTFromListBox(hwnd); // Get selection
             PrintResetJobProperties(hprt); // Reset job properties
             fNewJobProp = TRUE;         //  Remember JP changed
             return FALSE;

         case DID_CANCEL:
             WinDismissDlg(hwnd,FALSE);
             return FALSE;
         }
         break;
     }
     return WinDefDlgProc(hwnd,msg,mp1,mp2);
 }


 /***        HPRTFromListBox - Get selected printer
  *
  *        Entry
  *            hwnd - hwnd of Printer Setup dialog
  *
  *        Exit
  *            hprt of selected printer
  */
 HPRINTER HPRTFromListBox(HWND hwnd)
 {
     HWND    hwndLB;
     USHORT  i;

     // Get selection
     hwndLB = WinWindowFromID(hwnd, IDL_PRINTER);
     i = SHORT1FROMMR(WinSendDlgItemMsg(hwnd,IDL_PRINTER,LM_QUERYSELECTION,0L,

     // Get printer handle

     return (HPRINTER) (ULONG)WinSendMsg(hwndLB,LM_QUERYITEMHANDLE,MPFROMSHORT
 }


 /***        SaveJobProperties - Save current job properties in EA on file
  *
  *        Entry
  *            glb.hprt        - printer handle
  *            glb.pszFile - file name
  *
  *        Exit
  *            Job properties saved with file.
  */
 VOID SaveJobProperties(VOID)
 {
     static USHORT   cb;
     char *            pb;

     if (!glb.fNewJobProp)                // Nothing to save
         return;

     glb.fNewJobProp = FALSE;                // JP will be saved

     // Get job properties size

     WinSetPointer(HWND_DESKTOP,hptrWait); // Tell user we are being slow

     cb = 0;
     PrintQueryJobProperties(glb.hprt,&cb,pb); // Get size
     if (cb == 0)                        // No job properties to save
         return;                         //  Done
     pb = MemAlloc(cb);                        // Alloc buffer
     if (pb == NULL)                        // Could not get memory
         return;                         //  Done

     // Get job properties

     if (!PrintQueryJobProperties(glb.hprt,&cb,pb)) { // failed
         MemFree(pb);                        // Free buffer
         return;                         //  Done
     }

     // Save new Job Properties with data file

     EASetValue(glb.pszFile,pszJobProp,cb,pb);
     MemFree(pb);                        // Free buffer

     WinSetPointer(HWND_DESKTOP,hptrNormal); // Tell user we are done
 }


 /***        Message - Put up message box
  *
  *        Entry
  *            psz  - message
  *            hwnd - parent window
  *
  *        Exit
  *            None
  */
 VOID Message(char *psz, HWND hwnd)
 {
     HWND    hwndOwner;

     if (hwnd == NULL) {
         hwndOwner = glb.hwndClient;
         if (hwndOwner == NULL)
             hwndOwner = HWND_DESKTOP;
     }
     else
         hwndOwner = hwnd;

     WinMessageBox(
         HWND_DESKTOP,                // parent
         hwndOwner,                // owner
         psz,                        // message
         NULL,                        // caption (use default)
         NULL,                        // window id
         MB_OK | MB_APPLMODAL        // window style
     );
 }


 /***        QueryHCI - Query hardcopy capabilities for current form
  *
  *        Entry
  *            hdc - handle to DC
  *            phci - pointer to HCINFO structure
  *
  *        Exit-Success
  *            Returns TRUE
  *
  *        Exit-Failure
  *            Returns FALSE
  */
 BOOL QueryHCI(HDC hdc,HCINFO *phci)
 {
     LONG    cForms;
     LONG    i;
     LONG    rc;

     cForms = DevQueryHardcopyCaps(hdc,0L,0L,phci); // Get form count
     if (cForms == 0)
         return FALSE;                        // No forms!

     for (i=0; i<cForms; i++) {
         rc = DevQueryHardcopyCaps(hdc,i,1L,phci); // Get info for this form
         if (rc == 1) {                        // Got info
             if (phci->flAttributes & HCAPS_CURRENT) { // Have current form
                 return TRUE;                // Return data
             }
         }
     }
     return FALSE;
 }


 STUBS.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CPGREP\STUBS.ASM

 ; stubs.asm - a module of empty stubs to keep the BINDer from
 ; complaining.
 ;
 ; Created by Microsoft Corp. 1986

 .286p

         public DOSCREATETHREAD
         public DOSSEMCLEAR
         public DOSSEMREQUEST
         public DOSSEMSET
         public DOSSEMWAIT
         public DOSSLEEP

 code        segment byte public 'CODE'
         assume        cs:code

 DOSCREATETHREAD:
 DOSSEMCLEAR:
 DOSSEMREQUEST:
 DOSSEMSET:
 DOSSEMWAIT:
 DOSSLEEP:
         ret

 code        ends
         end


 SUSPEND.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SUSPEND\SUSPEND.C

 /*
  * Example of DosSuspendThread/DosResumeThread usage.
  *
  * DosSuspendThread can be used to ensure mutual exclusion when a thread
  * knows by number all the other threads which might try to access the
  * shared resource.
  *
  * In this example the main thread can call printf() freely because it
  * knows that the only other thread has been suspended, so the main
  * thread will not be interrupted, and because it knows that the
  * subthread must have been suspended while outside of a critical
  * section, and so the main thread will not be interrupting the
  * subthread's call to vio.
  *
  * Any thread may suspend any other thread in its process, including the
  * main thread and itself.  If a thread suspends all the threads in a
  * process, including itself, then deadlock will result, and the process
  * will have to be killed externally.
  *
  * Note that there are three methods for managing critical sections
  * amongst threads in a process:
  *
  *        1. Semaphores - this is almost always the right solution
  *        2. DosEnterCritSec
  *        3. DosSuspendThread
  *
  * compile as: cl -Gs -AL -G2 -Lp suspend.c
  *
  * Created by Microsoft Corp. 1986
  */

 #define INCL_DOSPROCESS
 #define INCL_SUB

 #include <os2def.h>
 #include <bse.h>
 #include "stdio.h"
 #include "malloc.h"
 #define STACK_SIZE        1024

 extern void f_thread(void);
 int flag;

 void main()
 {
         char *stkptr;
         TID thread_id;
         register int i;

         /* obtain pointer to the END of a block of memory */
         stkptr = (char *)malloc(STACK_SIZE) + STACK_SIZE - 1;

         /* create another thread */
         DosCreateThread(f_thread, &thread_id, stkptr);

         for(i = 0; i < 20; i++) {
                 DosSuspendThread(thread_id);        /* suspend the subthread

                 printf("the main thread has suspended thread %u\n", thread_id
                 DosSleep(3000L);

                 /* resume the subthread */
                 printf("now resuming the suspended thread\n");
                 DosResumeThread(thread_id);

                 DosSleep(3000L);
         }
         DosExit(EXIT_PROCESS, 0);           /* exit all threads */
 }

 void f_thread()
 {
         while (1) {
                 DosEnterCritSec();
                 VioWrtTTY("subthread running\r\n", 19, 0);
                 DosExitCritSec();
                 DosSleep(300L);
         }
 }


 SYMBOL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\SYMBOL.C

 /***************************************************************************\

 Module Name: SYMBOL.C

 This module contains the code for displaying symbols for a specified address.

 \***************************************************************************/

 #define INCL_DOSPROCESS
 #define        INCL_GPILCIDS
 #define        INCL_WINDIALOGS
 #define        INCL_WINFRAMEMGR
        INCL_WINHEAP                        /* needed for spy.h */
 #define        INCL_WININPUT
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMENUS
 #define        INCL_WINMESSAGEMGR
        INCL_WINPOINTERS                /* needed for spy.h */
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include <stdio.h>
 #include <string.h>
 #include "spy.h"
 #include "spyhook.h"
 #include "symbol.h"

 WHOISINFO whoIs;
 MAPDEF mapdef;
 MAPEND mapend;
 SEGDEF segdef;
 SYMDEF symdef;

 extern VOID lstrcat(PSZ, PSZ);
 extern void OutputString(char [], SHORT);       /* Output string to output de
 extern  BOOL FValidPointer (CHAR FAR *pVoid, SHORT cbStruct);
 extern  BOOL FGuessValidPointer (CHAR FAR *pVoid, SHORT cbStruct);


 extern HWND     hwndSpyFrame;
 extern HAB      hab;

 /* Function prototypes */
 HWND HwndFrameFromPID(PID);
 VOID GetSymbolInformation(PID, char far *, char *);
 VOID NEAR ProcessLNOkButton(HWND);
 MRESULT NEAR FormatLNDialog(HWND, PSWP, PRECTL);
 VOID NEAR EndLNDialog(HWND);
 MRESULT EXPENTRY ListNearDlgProc(HWND, USHORT, MPARAM, MPARAM);


 /***************************************************************************\
 * hwnd HwndFrameFromPID(pid)
 *
 * This function attempts to locate a frame window that was
 * created by the specified PID. It returns NULL if none are found.
 \***************************************************************************/
 HWND HwndFrameFromPID(pid)
 PID             pid;
 {
     HENUM       henum;
     HWND        hwnd;
     PID         pidWindow;
     TID         tidWindow;
     char        szClassName[50];    /* Class name of window */
     CLASSINFO   classinfo;          /* Information about class */

     henum = WinBeginEnumWindows(HWND_DESKTOP);
     if (henum == NULL)
         return (NULL);

     while ((hwnd = WinGetNextWindow(henum)) != NULL) {
         WinQueryWindowProcess(hwnd, &pidWindow, &tidWindow);
         WinLockWindow(hwnd, FALSE);
         if (pid == pidWindow) {
             /* See if a frame window */
             WinQueryClassName(hwnd, sizeof(szClassName),
                 (PSZ)szClassName);
             if (WinQueryClassInfo(hab, (PSZ)szClassName, &classinfo) &&
                     (classinfo.flClassStyle & CS_FRAME))
                 break;  /* We have our frame */
         }
     }

     WinEndEnumWindows(henum);
     return (hwnd);
 }



 /***************************************************************************\
 * int IdentifyCodeSegment(lpSegInfo, pid, selector)
 *
 *            Identifies the code selector for the specified process.
 *          NOTE: The process is currently ignored, but later will somehow
 *          return information in the context of the specified process ID.
 \***************************************************************************/
 int
 pascal near IdentifyCodeSegment( pSegInfo, pid, selector )
 PSEGINFO   pSegInfo;
 PID         pid;
 USHORT      selector;
 {
     char far *lp;
     char    *np;
     int     n;
     BYTE    c;
     USHORT  segnum;
     USHORT  seg_ptr;
     USHORT  uTemp;
     int     rc;
     PIDINFO pidinfo;
     HWND    hwndPIDFrame;

     /* If new selector matches the current one, then nothing to do */
     if ((pSegInfo->pid == pid) && (pSegInfo->selector == selector))
         return 0;

     /*
      * Try to identify the code selector.  We will first try in the context
      * of our process.  If this fails, or if we find that the segment is
      * associated with our program and it is not our PID, we will attempt
      * to get the symbol in the specified PID context, with the help of
      * our hooks.
      */
     DosGetPID(&pidinfo);
     rc = IdentifyCodeSelector( selector, (PWHOISINFO)&whoIs );
     if ((pid != pidinfo.pid) && (rc == 0)) {
         /*
          * User asked for a different context, and symbol was found.
          * If the symbol was in our .exe we will ignore what we found,
          * and try again.
          */
         /* First get past the module name */
         np = whoIs.names;
         while (*np++)
             ;
         /* Now go through the pathname */
         while (*np) {
             if (*np++ == '.') {
                 if (strcmp(np,"EXE") == 0) {
                     rc = 1;     /* It is in our exe, so set errr condition */
                     break;  /* no need to go on */
                 }
             }
         }
     }

     if (rc) {
         /*
          * We need to try in other PID context
          */
         if ((pid == pidinfo.pid) ||
                 ((hwndPIDFrame = HwndFrameFromPID(pid)) == NULL))
             return (rc);    /* segment not found or no way to find segment */

         /* Lets use the hook */
         SpySetLNSymbolPID(pid, selector);

         /* HACK: Send message to frame known to send another message */
         WinSendMsg(hwndPIDFrame, WM_QUERYTASKFOCUS, 0L, 0L);

         if (SpyGetLNSymbolSelector(&whoIs) != 0)
             return (1);     /* Still did not find the symbol */

     }


     /* It is a valid code selector, make it the new current selector */
     pSegInfo->selector = selector;
     pSegInfo->pid = pid;

     /* Is the new selector in the same module? */
     if (pSegInfo->lpSegName && pSegInfo->whoIs.mte == whoIs.mte)
         pSegInfo->whoIs.segNum = whoIs.segNum;     /* Yes, just copy seg# */
     else {

         /* New module, get segment #, mte handle, module name and pathstring
         pSegInfo->whoIs    = whoIs;

         /* Generate separate pointers to module name and pathstring */
         lp = &pSegInfo->whoIs.names[0];
         pSegInfo->lpModName = lp;
         while (*lp++)
             ;
         pSegInfo->lpPathName = lp;

         /* Change pathstring extension to be .SYM */
         while (*lp)
             if (*lp++ == '.') {
                 *lp++ = 'S';
                 *lp++ = 'Y';
                 *lp++ = 'M';
                 }
         lp++;

         /*
          * No current segment name or symbol name, so make them point to
          * null string
          */
         *lp = 0;
         pSegInfo->lpSegName = lp;
         pSegInfo->lpSymName = 0L;

         /* Close any currently open .SYM file and then open the new .SYM file
         if (pSegInfo->symfh != -1)
             pSegInfo->symfh = DosClose((HFILE)pSegInfo->symfh );

         if (DosOpen(pSegInfo->lpPathName, (PHFILE)&pSegInfo->symfh,
                 (PUSHORT)&uTemp,
                 0L, 0, 1, 0x00c0, 0L) == 0) {

             /* Read in .SYM file header and save segment count and pointer */

             DebugFileRead( pSegInfo->symfh, (PSZ)&mapdef, sizeof( mapdef ) );
             pSegInfo->segcnt = mapdef.seg_cnt;
             pSegInfo->segptr = mapdef.seg_ptr;

             /*        Read version of MAPSYM that produced this file.
                 If not a version 4 file then ignore the file */

             DebugFileSeek( pSegInfo->symfh, (long)-sizeof( mapend ), 2 );
             DebugFileRead( pSegInfo->symfh, (PSZ)&mapend, sizeof( mapend ) );
             if (mapend.ver != 4)
                 pSegInfo->symfh = DosClose( (HFILE)pSegInfo->symfh );
             }
         }

 /* Point to segment name buffer and make it a null string for now */

     lp = pSegInfo->lpSegName;
     *lp = 0;

 /* If we have an open .SYM file, search for segment definition record */

     if (pSegInfo->symfh != -1 && (SHORT) pSegInfo->whoIs.segNum < pSegInfo->s

     /* Start with first segment definition and read segNum definitions */

         seg_ptr = pSegInfo->segptr;
         n = pSegInfo->whoIs.segNum;
         while (n--) {
             DebugFileSeek( pSegInfo->symfh, (long)seg_ptr, 4 );
             DebugFileRead( pSegInfo->symfh, (PSZ)&segdef, sizeof( segdef ) );
             seg_ptr = (USHORT)segdef.nxt_seg;
         }

     /* Now read in the segment name string associated with this segment
        and make it a null terminated string */

         DebugFileRead( pSegInfo->symfh, (PSZ)(lp),
                        (int)((BYTE)segdef.nam_len) );
         lp += segdef.nam_len;
         *lp++ = 0;

     /* Remember the count of symbols for this segment */

         pSegInfo->symcnt = segdef.sym_cnt;


     /* Remember the file offset of the symbols for this segment, which is
        immediately after the segment definition record and name we just read

         pSegInfo->symFPos = DebugFileSeek( pSegInfo->symfh, 0L, 1 );
         }
     else {
     /* If no .SYM file or segment number too big, then make the segnemt name
        be the hex representation of its number */

         pSegInfo->symcnt = 0;

         segnum = pSegInfo->whoIs.segNum;
         n = 4;
         lp += n;
         while (n--) {
             c = (char)(segnum & (USHORT)0xF);
             segnum >>= 4;
             if (c > 9)
                 c += 'A' - 10;
             else
                 c += '0';
             *--lp = c;
             *lp++ = 0;
         }

     /* Null terminate the segment name string */

         lp += 4;
         *lp++ = 0;
     }

 /* Initialize the symbol name pointer to point to a null string, immediately
    after the segment name string just created above. */

     pSegInfo->lpSymName = lp;
     *lp = 0;

     return 0;
 }


 USHORT
 pascal near FindSymbol( pSegInfo, offset )
 PSEGINFO pSegInfo;
 USHORT offset;
 {
     USHORT i;
     long symPos;
     char far *lp;

 /* If no .SYM file then return failure */

     if (pSegInfo->symfh == -1)
         return 0xFFFF;

 /* No symbol found yet, so make it a null string */

     lp = pSegInfo->lpSymName;
     *lp = 0;

 /* Now do a linear search of the symbol definitions for this segment, looking
    for the closest match.  The definitions are stored in sorted order */

     symPos = DebugFileSeek( pSegInfo->symfh, (long)pSegInfo->symFPos, 0 );
     i = (USHORT)pSegInfo->symcnt;
     while (i--) {
         DebugFileRead( pSegInfo->symfh, (PSZ)&symdef, sizeof( symdef ) );

     /* If this is not our symbol then just remember its offset */

         if ((USHORT)symdef.sym_val <= offset) {
             symPos = DebugFileSeek( pSegInfo->symfh, 0L, 1 ) -
                      sizeof( symdef );

         /* If exact match, exit this loop */

             if ((USHORT)symdef.sym_val == offset)
                 break;
             }
         else
         /* If we went past then exit this loop */

             break;

     /* Still looking, so seek past this symbols name string to next symbol
        definition */

         DebugFileSeek( pSegInfo->symfh, (long)symdef.nam_len, 1 );
         }

 /* Seek to found symdebol definition and read it in, along with the name */

     DebugFileSeek( pSegInfo->symfh, (long)symPos, 0 );
     DebugFileRead( pSegInfo->symfh, (PSZ)&symdef,
                    sizeof( symdef ) );
     DebugFileRead( pSegInfo->symfh, (PSZ)lp,
                    (int)((BYTE)symdef.nam_len) );

 /* Make the name a null terminated string */

     lp += symdef.nam_len;
     *lp++ = 0;

 /* Return the distance of the passed offset from the value of the symbol */

     return offset - symdef.sym_val;
 }



 /*************************************************************************
  *
  * GetSymbolInformation(pid, pvoidInfo, pszInfo)
  *
  * Purpose: To get information about the supplied address
  */
 VOID GetSymbolInformation(pid, pvoidInfo, pszInfo)
 PID             pid;
 char far        *pvoidInfo;
 char            *pszInfo;
 {
     SEGINFO     seginfo;
     char        szTemp[256];
     USHORT      uOffsetFromSym;


     /*
      * First try to find the segment in the segment table.
      */
     if (IdentifyCodeSegment(&seginfo, pid, SELECTOROF(pvoidInfo)) != 0) {

         /*
          * It failed to find the segment, ie it was invalid, so
          * Simply return INVALID to caller
          */
         sprintf(pszInfo,"PID:%d %04x:%04x - INVALID", (SHORT)pid,
                 SELECTOROF(pvoidInfo), OFFSETOF(pvoidInfo));
         return;
     }


     sprintf(pszInfo,"PID:%d %04x:%04x - %Fs ! ", (SHORT)pid,
             SELECTOROF(pvoidInfo), OFFSETOF(pvoidInfo),
             seginfo.lpModName);

     if (seginfo.symfh == -1) {
         sprintf(szTemp,"%04x", seginfo.whoIs.segNum);
         strcat(pszInfo, szTemp);
     } else {
         lstrcat((PSZ)pszInfo, seginfo.lpSegName);
     }

     uOffsetFromSym = FindSymbol(&seginfo, OFFSETOF(pvoidInfo));
     if (uOffsetFromSym != 0xffff) {
         strcat(pszInfo, ":");

         lstrcat((PSZ)pszInfo, seginfo.lpSymName);
         if (uOffsetFromSym != 0) {
             sprintf(szTemp, "+%x", uOffsetFromSym);
             strcat(pszInfo, szTemp);
         }
     }
 }


 /***************************************************************************\
 * VOID NEAR ProcessLNOkButton(hwndDlg)
 *
 * Process the Ok Button for the List Near Dlg proc
 \***************************************************************************/
 VOID NEAR ProcessLNOkButton(hwndDlg)
 HWND hwndDlg;
 {
     char        szTemp[100];
     char far    *pVoid;
     PID         pid;
     SHORT       item;

     /*
      * For now try using sscanf to convert the string into a
      * pointer.
      */
     WinQueryDlgItemShort(hwndDlg, DID_PID, &pid, FALSE);
     WinQueryDlgItemText(hwndDlg, DID_ADDR, sizeof(szTemp), szTemp);
     if ((strchr(szTemp,':') != NULL) && (sscanf(szTemp,"%p", &pVoid)) > 0) {
         /* It translated, so try to get info about pointer */
         GetSymbolInformation(pid, pVoid, szTemp);

         /* For now simply output to output areas */
         item = SHORT1FROMMR(WinSendDlgItemMsg(hwndDlg, DID_SYMLIST,
                 LM_INSERTITEM, (MPARAM)LIT_END, (MPARAM)(PSZ)szTemp));

         WinSendDlgItemMsg(hwndDlg, DID_SYMLIST, LM_SETTOPINDEX,
                 (MPARAM)item, 0L);
     }

     /*
      * Now clear the text out of the ADDR field, and set the focus
      * back to the window
      */
     WinSetDlgItemText(hwndDlg, DID_ADDR, "");
     WinSetFocus(HWND_DESKTOP, WinWindowFromID(hwndDlg, DID_ADDR));
 }

 /***************************************************************************\
 * MRESULT NEAR FormatLNDialog(hwndDlg, paswp, prectl)
 *
 * Process the WM_SIZE message in the List Near Dialog
 \***************************************************************************/
 MRESULT NEAR FormatLNDialog(hwndDlg, paswp, prclClientX)
 HWND                hwndDlg;
 PSWP                paswp;
 PRECTL              prclClientX;
 {
     USHORT          cy;
     USHORT          cswp;
     HPS             hps;
     SHORT           dy;
     register HWND   hwndT;
     FONTMETRICS     fm;
     PSWP            pswpT;
     RECTL           rclClient;
     SWP             swpOk;
     SHORT           y;

     /*
      * First let the default frame processing set up the position of
      * all of the standard controls.  Then we will position the list box
      * and the two entry fields to fill in the remainder of the space.
      */

 #define PSWPLN(x) (paswp + cswp + x - DID_SYMLIST)


     cswp = SHORT1FROMMR(WinDefDlgProc(hwndDlg, WM_FORMATFRAME, (MPARAM)paswp,
             (MPARAM)(PRECTL)&rclClient));
     if (prclClientX != NULL)
         *prclClientX = rclClient;   /* Give caller info */


     /*
      * Now Add our controls to the SWP list - Verify that the first one
      * exists. If not, we probably are initializing the dialog, so
      * Simply return what the default Dlg Proc returned.
      */
     hwndT = WinWindowFromID(hwndDlg, DID_OK);
     if (hwndT == NULL)
         return ((MRESULT) cswp);

     /*
      * We need to get a PS, such that we can find out the size of the
      * font, to adjust our edit fields such that they wont keep moving
      * in.
      */
     hps = WinGetPS(hwndDlg);
     GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);
     WinReleasePS(hps);

     WinQueryWindowPos(hwndT, &swpOk);

     pswpT=PSWPLN(DID_SYMLIST);
     WinQueryWindowPos(hwndT = WinWindowFromID(hwndDlg, DID_SYMLIST), pswpT);
     dy = pswpT->y - (swpOk.y + swpOk.cy);
     pswpT->fs = SWP_SIZE;
     pswpT->cx = (SHORT)rclClient.xRight - (SHORT)rclClient.xLeft - pswpT->x;

     /* First position the top line, just below titlebar */
     pswpT=PSWPLN(DID_PIDLABEL);
     WinQueryWindowPos(hwndT = WinWindowFromID(hwndDlg, DID_PIDLABEL), pswpT);
     pswpT->fs = SWP_MOVE;
     cy = pswpT->cy;
     y = pswpT->y = (SHORT)rclClient.yTop - (SHORT)rclClient.yBottom -
             cy - dy;

     pswpT=PSWPLN(DID_PID);
     WinQueryWindowPos(hwndT = WinWindowFromID(hwndDlg, DID_PID), pswpT);
     pswpT->fs = SWP_MOVE;
     pswpT->x += (SHORT)fm.lAveCharWidth / 2; /* Take care of margin */
     pswpT->y = y;

     /* Since Edit control is bigger Setup Start Y for the next row */
     y += (cy - pswpT->cy);


     /* Now position Addr line below PID line */
     pswpT=PSWPLN(DID_ADDRLABEL);
     WinQueryWindowPos(hwndT = WinWindowFromID(hwndDlg, DID_ADDRLABEL), pswpT)
     pswpT->fs = SWP_MOVE;
     y = (pswpT->y = y - pswpT->cy - dy);

     pswpT=PSWPLN(DID_ADDR);
     WinQueryWindowPos(hwndT = WinWindowFromID(hwndDlg, DID_ADDR), pswpT);
     pswpT->fs = SWP_MOVE;
     pswpT->x += (SHORT)fm.lAveCharWidth / 2; /* Take care of margin */
     pswpT->y = y;

     /* Now set size of listbox */
     y += (cy - pswpT->cy);

     pswpT=PSWPLN(DID_SYMLIST);
     pswpT->cy = y - dy - pswpT->y;


     /*
      * Return the count of swps
      */
     return ((MRESULT)(cswp + (DID_PID-DID_SYMLIST+1)));
 }




 /***************************************************************************\
 * VOID NEAR EndLNDialog(hwndDlg)
 *
 * End processing for the List Near Dialog
 \***************************************************************************/
 VOID NEAR EndLNDialog(hwndDlg)
 HWND hwndDlg;
 {
     /* First Dismiss the dialog and destroy the windows */
     WinDismissDlg(hwndDlg, 0);
     WinDestroyWindow(hwndDlg);

     /* And re-enable the menu item */
     WinSendMsg(WinWindowFromID(hwndSpyFrame, FID_MENU),
         MM_SETITEMATTR, MPFROM2SHORT(CMD_LISTNEAR, TRUE),
         MPFROM2SHORT(MIA_DISABLED, 0));

 }



 /***************************************************************************\
 * MRESULT EXPENTRY ListNearDlgProc(hwnd, msg, mp1, mp2)
 *
 * Will locate the symbol near the specified address.
 \***************************************************************************/
 MRESULT EXPENTRY ListNearDlgProc(hwnd, msg, mp1, mp2)
 HWND            hwnd;
 USHORT          msg;
 MPARAM          mp1;
 MPARAM          mp2;
 {
     PIDINFO     pidinfo;

     switch (msg) {

     case WM_INITDLG:
         /* Initialize the dialog items */
         WinSetDlgItemText(hwnd, DID_ADDR, "");
         DosGetPID(&pidinfo);
         WinSetDlgItemShort(hwnd, DID_PID, (USHORT)pidinfo.pid, FALSE);
         break;

     case WM_SYSCOMMAND:
         if (SHORT1FROMMP(mp1) == SC_CLOSE) {
             /* End processing the dialog */
             EndLNDialog(hwnd);
             break;
         } else
             return(WinDefDlgProc(hwnd, msg, mp1, mp2));


     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:

             /*
              * Process The Ok Button, by caling ProcessLNOkButton, which
              * will add the information to the listbox in our dialog.
              */
             ProcessLNOkButton(hwnd);
             break;

         case DID_CANCEL:
             /* End processing the dialog */
             EndLNDialog(hwnd);
             break;
         }

         break;

     /*
      * We want to add our controls into the formating of the frame, so
      * we need to add some room onto the SWP list
      */
     case WM_QUERYFRAMECTLCOUNT:
         return((MRESULT)(SHORT1FROMMR(WinDefDlgProc(hwnd, msg, mp1, mp2)) +
                 (SHORT)(DID_PID-DID_SYMLIST+1)));
         break;

     /*
      * Call FormatLNDialog, to format the dialog
      */
     case WM_FORMATFRAME:
         return (FormatLNDialog(hwnd, (PSWP)mp1, (PRECTL)mp2));

     default:
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
     }

     return (0L);
 }


 TED.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TED\TED.C

 /****************************************************************************

     TED.C - Tiny Editor: Sample application

     Created by Microsoft Corporation, IBM Corporation 1989

 -----------------------------------------------------------------------------

     This module contains the main processing elements of the TED sample
     application.  This application is a very simple editor which utilizies
     an MLE to do all the text interaction with the user.  Full help support
     is built in using Help Manager. The application is organized into the
     following 15 files.


         Ted.c     - Contains the main processing loop and all the MLE handler
                     functions.

         Ted.h     - Contains the #define and function prototypes.

         Ted.rc    - Contains the Menu entries, the accelerator table and
                     the string resources.

         Ted.ico   - The icon that appears when Ted is minimized and also
                     in the about box.

         Ted.def   - Module definition file for Ted.


         Tedmem.c  - Contains the memory allocation functions.

         Tedmem.h  - Contains the #define and the function prototypes for
                     the memory allocation functions.


         Tedhelp.c - Contains the help manager support functions.

         Tedhelp.h - Contains all the ID's for the Help Manager help panels

         Tedhelp.rc- Contains the Help Manager tables and sub-tables
                     that associate each selectable item with a particular
                     help panel in the Tedhp.itl file.


         Tedhp.itl - Contains the Help Manager help panels.

         Tedhp.h   - Contains the #defines for the help panel id's

         Teddlg.dlg- Contains the Dialog templates for the Find and
                     about dialog boxes.

         Teddlg.h  - Contains the #define ID's for the Find and about dialog
                     boxes


         MAKEFILE  - The makefile (nmake.exe) for the TED application.




     DLL's used:


         opendlg.dll - This dll is provided in the toolkit and it contains
                       the open/save as dialog used for choosing files.
                       The directory it resides in must be specified in
                       the LIBPATH.

     Import libraries used:


         opendlg.lib - This contains the functions used to interface
                       to the open/save dialog DLL.

         opendlg.h   - This contains the functions prototypes etc
                       used to interface to the open/save dialog DLL.


     Help Libraries used:


         tedhp.hlp   - This is the Help Manager help panel storage file
                       It must be either in the directory specified by
                       the HELP environment variable, in \OS2\HELP, or
                       in the current directory.



         Modification History

             891005    James Bratsanos, Updated comment so help file (.hlp)
                                        can now be in current directory.

             890911    James Bratsanos, Created



 ****************************************************************************/


 /****************************************************************************

     Include Files, Macros, Defined Constants

 ****************************************************************************/

 #define INCL_WIN
 #define INCL_DOS

 #include <os2.h>

 #include <stdio.h>
 <opendlg.h>    /* Header file for toolkit open/save dialogs */
 #include <string.h>

 #include "ted.h"
 #include "tedhelp.h"
 #include "teddlg.h"
 #include "tedmem.h"



 /****************************************************************************

     Variables Used Globally in this module

 ****************************************************************************/

 HWND  hwndFrame   = NULL;   /* The window handle of the frame */
 HWND  hwndMenu    = NULL;   /* The window handle of our menu  */
 HWND  hwndMLE     = NULL;   /* The window handle of the MLE (Client of Frame*
 HWND  hwndDlgFind = NULL;   /* The window handle of the FIND dialog box */
 HAB   hab         = NULL;   /* The anchor block handle for TED */
 PFNWP pfnwpold    = NULL;   /* A pointer to the old window procedure of */
                             /* the Client (the MLE window proc )        */

 HMQ   hmq ;                 /* Message queue handle */

 PFNWP pfnwpoldframe = NULL; /* A pointer to the old window procedure of */
                             /* the Frame */


 struct _TEDINFO {           /* Used to keep track of the file name and  */
                             /* path currently being edited              */

     UCHAR uchFileName[ CCHMAXPATH ];         /* Just the file name */
     UCHAR uchFullPathName[ CCHMAXPATH];      /* The full path      */
     UCHAR uchPgmName[  CCHMAXPATH ];         /* Program name from title bar *

 };

 struct _TEDINFO tedinfo;



 /****************************************************************************

     main - This is the main procedure of the ted application.  It does
            any initialization that is required goes into the standard
            get / dispatch message loop and, finally does any cleanup
            that is required.

 ****************************************************************************/

 VOID main (VOID)
 {


     static ULONG flFrameFlags = FCF_STANDARD;
     QMSG         qmsg ;     /* Information used to dispatch msg's */


     /********************************************************************
         Create the anchor block (HAB) and the message queue for our
         application.
     ********************************************************************/



     hab = WinInitialize (0) ;
     hmq = WinCreateMsgQueue (hab, 0) ;



     /********************************************************************
         Create the frame window and specify that the client is an MLE
         So this way the MLE will handle all the WM_SIZE, WM_PAINT msg
         and we dont have to.
     ********************************************************************/


     hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
                                          WS_VISIBLE | MLS_WORDWRAP |
                                                  FS_ACCELTABLE,
                                     &flFrameFlags,
                                     WC_MLE ,
                                     NULL,
                                     WS_VISIBLE | MLS_HSCROLL | MLS_VSCROLL |
                                                    MLS_WORDWRAP ,
                                     (HMODULE) NULL,
                                     ID_RESOURCE,
                                     &hwndMLE ) ;


     if ( hwndFrame == NULL || hwndMLE == NULL )
     {
         TEDDisplayErrorID( SID_UNABLE_TO_CREATE_FRAME  );

         TEDCleanupExit();      /* Cleanup and exit , NOTE: never returns!! */
     }



     /*  Sub class the frame so we get the WM_CONTROL MSG's from the MLE */



     if ( ( pfnwpoldframe = WinSubclassWindow( hwndFrame, FrameWndProc ))
                                   == (PFNWP) 0 )
     {
         TEDDisplayErrorID( SID_UNABLE_TO_SUB_FRAME );

         TEDCleanupExit();      /* Cleanup and exit , NOTE: never returns!! */
     }

     /********************************************************************
         Subclass the client window in order to receive the MSG's from
         our menu.
     ********************************************************************/


     if ( (pfnwpold = WinSubclassWindow( hwndMLE , ClientWndProc ))
                                        ==   (PFNWP) 0 )
     {
         TEDDisplayErrorID( SID_UNABLE_TO_SUB_CLIENT );

         TEDCleanupExit();      /* Cleanup and exit , NOTE: never returns!! */
     }


     /********************************************************************
         Since the MLE sends messages to its owner and WinCreateStdWindow
         does not assign an owner to its client then set the owner of the
         MLE to be the frame (Since we subclassed the frame window
         earlier we now receive the WM_CONTROL (notification messages)
         from the MLE
     ********************************************************************/


     if ( !WinSetOwner( hwndMLE, hwndFrame ))
     {
         TEDDisplayErrorID( SID_UNABLE_TO_SET_FRAME_OWNER );

         TEDCleanupExit();      /* Cleanup and exit , NOTE: never returns!! */
     }



     MLEInit ( hwndMLE );



     /*  Save off the id of our menu so we can send it msg's as required. */


     hwndMenu = WinWindowFromID(hwndFrame, FID_MENU) ;



     /*  Set the initial state of the Word Wrap menu item on our menu */


     MLESetWrap( hwndMLE, hwndMenu, FALSE );

     /*****************************************************************
         Init the Help Manager. No need to check for success since TED
         will function fine without help.
     *****************************************************************/


     TEDInitHelp( hwndFrame, hab );



     /* Get the name of the program so we can use it later in the title */

     WinQueryWindowText( hwndFrame ,
                         sizeof(tedinfo.uchPgmName ),
                         tedinfo.uchPgmName);



     /********************************************************************
         Setup the path name portion of our program to be NULL and the
         title to be Untitled
     ********************************************************************/


     TEDInitFileName();


     TEDUpdateTitleBar( hwndFrame, tedinfo.uchPgmName, tedinfo.uchFileName );



     /********************************************************************
         Stay in the standard Presentation manager processing loop
         until a WM_QUIT is received. This causes WinGetMsg() to
         return FALSE and thus the loop is exited.
     ********************************************************************/


     while(WinGetMsg (hab, &qmsg, NULL, 0, 0))
     {
         WinDispatchMsg (hab, &qmsg) ;
     }



     /********************************************************************
         Do any cleanup the help manager requires. We do not need
         to cleanup the ABOUT dialog since it is modal and is destroyed
         every time the user removes it.
     ********************************************************************/


     TEDTerminateHelp( hwndFrame );



     TEDCleanupExit();     /* Cleanup and exit , NOTE: never returns!! */


 }



 /**********************************************************************

     TEDInitFileName - This function initalizes the full path of the
                       current file to be NULL and the current file
                       name to be "Untitled". This way the save dialog
                       need only check if the full path is null and
                       thus it will know that no file has been set.
                       As soon as we do a OPEN or SAVE AS then the full
                       path will be set to something other than NULL
                       and thus the file will no longer be considered
                       Untitled.


 **********************************************************************/


 VOID TEDInitFileName()
 {


     /* Load the string that will signifiy an Untitled file name */


     if ( WinLoadString( hab,
                         (HMODULE) NULL,
                         SID_TED_UNTITLED,
                         sizeof( tedinfo.uchFileName),
                         tedinfo.uchFileName )  == (SHORT) 0 )
     {
         MESSAGE("Cannot load untitled string ");
     }

     tedinfo.uchFullPathName[0] = '\000';   /* Make the path empty */

 }




 /**********************************************************************

     FrameWndProc - This window procedure is used to catch
                    notification messages (WM_CONTROL) from the MLE
                    and also Help Manager errors.  This procedure
                    actually is subclassed on the frame and all
                    non processed messages are passed through to the
                    default frame proc.

 **********************************************************************/

 MRESULT EXPENTRY FrameWndProc( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {

     MRESULT mresult;    /* Used to return Help Panel ID to Help Manager */


     switch( msg )
     {
         case WM_CONTROL:                          /* Control Message      */

             switch( LOUSHORT( mp1 ) )             /* ID of control        */
             {
                 case FID_CLIENT :                 /* Verify its from MLE  */


                     switch( HIUSHORT( mp1 ) )     /* Notification Message */
                     {

                     /******************************************************
                         The MLE will send a MLN_CHANGE message when
                         something in the MLE is modified. This could
                         be used to do smart closing where TED would
                         notify the user if an exit attempt was made
                         when the MLE text had unsaved changes.
                     ******************************************************/

                         case MLN_CHANGE:

                         /* Just do the old frame procedure processing */

                             break;

                     /******************************************************
                         The MLE will send a MLN_TEXTOVERFLOW message when
                         a key stroke would cause the text in the MLE to
                         exceed the TEXT_LIMIT set
                     ******************************************************/

                         case MLN_TEXTOVERFLOW:

                             TEDDisplayErrorID( SID_MLEMSG_TEXTOVERFLOW );

                             return FALSE; /* Tell MLE to not allow overflow *

                             break;


                     /******************************************************
                         The MLE will send a MLN_OVERFLOW message when
                         an action other that a key stroke would cause
                         the text in the MLE to exceed the allowable
                         amount, or the format rectangle would have
                         been inadequate to contain the text.

                         NOTE: mp2 is a pointer to a OVERFLOW structure
                               containing detailed information as to
                               what caused the overflow.
                     ******************************************************/


                         case MLN_OVERFLOW:

                             TEDDisplayErrorID( SID_MLEMSG_MLN_OVERFLOW );

                             return FALSE; /* Tell MLE to not allow overflow *

                             break;


                         default:

                         /* Just do the old frame procedure processing */

                             break;

                     } /* End switch( HIUSHORT( mp1 )) */
                     break;

                 default :

                 /* Just do the old frame procedure processing */

                     break;


             }  /* End switch( LOUSHORT( mp1 ) ) */


         /* Just do the old frame procedure processing */

             break;


         default:

         /********************************************************************
             Process any help messages that come through.
             If TEDProcessHelpMsg() can handle any of the Help messages it
             receives the TRUE is returned and we return whatever was
             passed back. Other wise the code falls through to the default
             window procedure.
         ********************************************************************/


             if ( TEDProcessHelpMsg( hwnd, msg, mp1, mp2, &mresult ) )
             {
                 return( mresult );
             }

             break;

     }  /* End switch( msg ) */


     return( (*pfnwpoldframe) (hwnd, msg, mp1, mp2)) ;


 }





 /**********************************************************************

     ClientWndProc - This procedure is the window proc (subclassed) for
                     the MLE. It handles any of the menu selections made
                     in our menu and passes everything else through to
                     the default window procedure (which is the MLE )

 **********************************************************************/



 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
 {
     CHAR achtmp[50];  /* Used to format the string when the user chooses the
                       /* Query lines , menu choice to see how many lines
                       /* there currently are in the MLE

     LONG     lFileSize;       /* The amount of text in the MLE */
     HFILE    hf;              /* File handle used for reading/writing */
     USHORT   usAction;        /* Action field of DosOpen call */


     switch (msg)
     {


         case WM_COMMAND:

             switch( LOUSHORT(mp1) )    /* Switch on Menu ID */
             {

                       case IDM_NEWFILE :

                 /********************************************************
                     If we disable the MLE then it wont try to keep the
                     display current while were deleting its contents.
                     This way the MLE pointer will turn to an hour glass,
                     all its text will be deleted (by selecting all the
                     text in the MLE and deleting it) then the hour glass
                     will be changed back to the system pointer.  This
                     way it appears better to the user since he does not
                     visually see all the text in the mle get selected
                     and then deleted.
                 ********************************************************/

                     MLEDisable( hwndMLE );

                     MLEDeleteContents( hwndMLE );

                     MLEEnable( hwndMLE );

                     TEDInitFileName();

                     TEDUpdateTitleBar( hwndFrame,
                                        tedinfo.uchPgmName,
                                        tedinfo.uchFileName );

                     break ;

                 case IDM_OPENFILE :


                     MLEOpenFile( hwnd,
                                  tedinfo.uchFileName,
                                  tedinfo.uchFullPathName );

                     TEDUpdateTitleBar( hwndFrame,
                                        tedinfo.uchPgmName,
                                        tedinfo.uchFileName );

                     break ;

                 case IDM_SAVE:

                 /************************************************************
                     If uchFullPathName is empty then the file name is
                     Untitled so we cannot do a save (since we have no name)
                     so send a message to ourselves as though the user had
                     clicked on the Save as menu item. If the file name is
                     not Untitled then open the current file and save the
                     contents of the MLE.
                 ************************************************************/

                     if ( tedinfo.uchFullPathName[0] == '\000' )
                     {

                         return ( WinSendMsg( hwnd,
                                              WM_COMMAND,
                                              MPFROMSHORT( IDM_SAVEAS ),
                                              mp2 ));


                     }
                     else
                     {


                         if ( DosOpen( tedinfo.uchFullPathName,
                                       &hf,
                                       &usAction,
                                       (ULONG) 0,
                                       (USHORT) 0,
                                       FILE_OPEN,
                                       OPEN_ACCESS_WRITEONLY |
                                         OPEN_SHARE_DENYWRITE,
                                       (ULONG) 0 ) )
                         {

                             TEDDisplayErrorID( SID_CANT_OPEN_EX_FILE );

                         }
                         else
                         {

                             MLESaveToFile( hf );
                             DosClose( hf );

                         }

                     }
                     break;

                 case IDM_SAVEAS :

                     MLESaveFile( hwnd,
                                  tedinfo.uchFileName,
                                  tedinfo.uchFullPathName );

                     TEDUpdateTitleBar( hwndFrame,
                                        tedinfo.uchPgmName,
                                        tedinfo.uchFileName );

                     break ;

                 case IDM_FIND :



                 /**********************************************************
                     If the handle to our Find dialog is not null then the
                     Find dialog is loaded so we need only show the window
                     and then give it the focus. If the handle is NULL then
                     the Find dialog has never been loaded so we need to
                     load it.  We use WinLoadDlg() because the Find dialog
                     is modeless ie the user can switch between the find
                     dialog and the mle without first having to terminate the
                     find dialog
                 **********************************************************/

                     if ( hwndDlgFind != NULL )
                     {

                         WinShowWindow( hwndDlgFind, TRUE );
                         WinSetFocus( HWND_DESKTOP, hwndDlgFind );


                     }
                     else
                     {

                          if ( (hwndDlgFind = WinLoadDlg(HWND_DESKTOP,
                                                        hwnd,
                                                        FindDlgProc,
                                                        (HMODULE) NULL,
                                                        DLG_FIND,
                                                        NULL )) == NULL )
                         {

                             TEDDisplayErrorID( SID_DLG_CANT_LOAD_FIND );

                         }
                     }
                     break ;

             /***********************************************************
                 Since the MLE supports the CUT, COPY PASTE and UNDO we
                 need only send the apropriate msg to the MLE and let it
                 do all the work
             ***********************************************************/

                 case IDM_CUT          :

                     WinSendMsg( hwndMLE, MLM_CUT, NULL, NULL );
                     break ;

                 case IDM_COPY          :

                     WinSendMsg( hwndMLE, MLM_COPY, NULL, NULL );
                     break;

                 case IDM_PASTE   :

                     WinSendMsg( hwndMLE, MLM_PASTE, NULL, NULL ) ;
                     break ;

                 case IDM_UNDO          :

                     WinSendMsg( hwndMLE, MLM_UNDO, NULL, NULL );
                     break ;

                 case IDM_SELECT_ALL :

                 /**********************************************************
                     In order to Select all the text in the MLE we need to
                     first query the mle as to the amount of text that is
                     currently in it. Then we send a message to the mle
                     to select the text from the beggining of the mle (IPT 0)
                     to the amount of text the mle told us it had in it.
                 **********************************************************/

                     lFileSize= (LONG) WinSendMsg( hwndMLE ,
                                                    MLM_QUERYTEXTLENGTH,
                                                        NULL,
                                                   NULL );

                     WinSendMsg(hwndMLE,
                                MLM_SETSEL,
                                (MPARAM) 0L,
                                (MPARAM) lFileSize);

                     break;


                 case IDM_QUERYLINES:

                 /* achtmp can only hold a message that is 50 bytes long */

                     sprintf( achtmp, "%ld Lines in Window",
                                            (LONG) WinSendMsg( hwndMLE,
                                                             MLM_QUERYLINECOUN
                                                         NULL,
                                                         NULL )) ;

                      MESSAGE( achtmp );
                     break;


                 case IDM_SETWRAP :

                     MLESetWrap( hwndMLE, hwndMenu, TRUE );
                     break ;


                 case IDM_DISPLAY_HELP:

                     TEDSendHelpMsg( HM_DISPLAY_HELP );
                     break;


                 case IDM_EXT_HELP:

                     TEDSendHelpMsg( HM_EXT_HELP );
                     break;

                 case IDM_INDEX_HELP:

                     TEDSendHelpMsg( HM_HELP_INDEX );
                     break;


                 case IDM_KEYS_HELP:

                     TEDSendHelpMsg( HM_KEYS_HELP );
                     break;

                 case IDM_ABOUT   :      /* About TED dialog */

                     if ( WinDlgBox( HWND_DESKTOP,
                                     hwndMLE,
                                     AboutDlgProc,
                                     (HMODULE) NULL,
                                     DLG_ABOUT,
                                     NULL )  == DID_ERROR )
                     {

                         TEDDisplayErrorID( SID_DLG_CANT_LOAD_ABOUT );

                     }
                     break ;

                 default:

                 /* Fall through to old procedure processing */

                     break;


             }  /* End switch ( LOUSHORT( mp1 )) */

             break ;

         default:

         /* Fall through to old procedure processing */

             break;



     }  /* End switch ( msg ) */

     return( (*pfnwpold) (hwnd, msg, mp1, mp2)) ;
 }




 /**********************************************************************

     AboutDlgProc - This procedure is the dialog procedure for the
                    About box.

 **********************************************************************/


 MRESULT EXPENTRY AboutDlgProc( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {


     switch( msg )
     {

         case WM_INITDLG:

             TEDInitDlgSysMenu( hwnd );    /* setup the system menu */

             break;

         case WM_CLOSE:

             if ( !WinDismissDlg(hwnd, TRUE))
             {
                 TEDDisplayErrorID( SID_DLG_FIND_CANT_DISMISS );

                 TEDCleanupExit(); /* Cleanup and exit , NOTE: never returns!!
             }


             return((MRESULT)TRUE);

             break;


         case WM_COMMAND:                        /* Process the Buttons  */


             switch (LOUSHORT(mp1))
             {

                 case DID_OK:                    /* OK */

                     if ( !WinDismissDlg( hwnd, TRUE ))
                     {
                         TEDDisplayErrorID( SID_DLG_FIND_CANT_DISMISS );
                     }

                     return(MRESULT) TRUE;

                     break;

                 default:

                     return(MRESULT) TRUE;
                     break;
             }

         default:

             return( WinDefDlgProc( hwnd, msg, mp1, mp2 ) );

             break;

     }
     return (MRESULT) FALSE;
 }



 /**********************************************************************

     FindDlgProc - This procedure is the dialog procedure for the
                   Find dialog box.  It is modeless so the user
                   is allowed to switch between the Find dialog box
                   and the MLE.

 **********************************************************************/



 MRESULT EXPENTRY FindDlgProc( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 {
     UCHAR   auchFindText[MAX_STRING_SIZE];  /* The find text from the     */
                                             /* FIND dialog box            */

     UCHAR   auchChangeText[MAX_STRING_SIZE];/* The change to text from    */
                                             /* the FIND dialog box        */

     ULONG   ulSearchFlags;                  /* Search flags passed to MLE */


     MLE_SEARCHDATA seMLESearchData;         /* The MLE search structure   */


     switch (msg)
     {

         case WM_INITDLG:


         /*******************************************************************
             Send a msg to each of the entry fields which contain the
             FIND and CHANGE TO text in order to limit the size to the
             buffer we have allocated to receive that text.
         *******************************************************************/

             WinSendMsg( WinWindowFromID( hwnd, DID_FIND_TEXT),
                         EM_SETTEXTLIMIT,
                         MPFROMSHORT( MAX_STRING_SIZE),
                         NULL );


             WinSendMsg( WinWindowFromID( hwnd, DID_CHANGE_TEXT),
                         EM_SETTEXTLIMIT,
                         MPFROMSHORT( MAX_STRING_SIZE),
                         NULL );


             TEDInitDlgSysMenu( hwnd );


             break;


         case WM_COMMAND:


             switch (SHORT1FROMMP(mp1))
             {

                 case DID_FIND_CHANGE_ALL:


                 /* Get the FIND and CHANGE TO text the user entered */

                     WinQueryWindowText( WinWindowFromID( hwnd, DID_FIND_TEXT)
                                         MAX_FNAME_LEN,
                                         auchFindText );


                     WinQueryWindowText( WinWindowFromID( hwnd, DID_CHANGE_TEX
                                         MAX_FNAME_LEN,
                                         auchChangeText );


                     ulSearchFlags = MLFSEARCH_CHANGEALL;

                     if ( WinSendMsg( WinWindowFromID(hwnd,DID_SENSITIVE),
                                      BM_QUERYCHECK,
                                      NULL,
                                      NULL))
                     {

                         ulSearchFlags |= MLFSEARCH_CASESENSITIVE;
                     }


                 /* Fill the MLE search structure with the search/change data

                     seMLESearchData.cb         = sizeof( seMLESearchData );

                     seMLESearchData.pchFind    = auchFindText;
                     seMLESearchData.cchFind    = strlen( auchFindText );

                     seMLESearchData.pchReplace = auchChangeText;
                     seMLESearchData.cchReplace = strlen( auchChangeText );

                     seMLESearchData.iptStart   =  0; /* Start at cursor IPT
                     seMLESearchData.iptStop    = -1; /* Search to end of MLE



                     if ( seMLESearchData.cchFind ) /* Non-empty string to
                     {                              /* to search for



                         if ( !WinSendMsg( hwndMLE,
                                           MLM_SEARCH,
                                           (MPARAM) ulSearchFlags,
                                           (MPARAM) &seMLESearchData ) )
                         {

                             TEDDisplayErrorID( SID_CANT_FIND );

                         }
                         else
                         {

                             WinSetFocus( HWND_DESKTOP, hwndMLE );

                         }

                     }
                     break;



                 case DID_FIND:


                     WinQueryWindowText( WinWindowFromID( hwnd, DID_FIND_TEXT)
                                         MAX_FNAME_LEN,
                                         auchFindText );


                     ulSearchFlags = MLFSEARCH_SELECTMATCH;

                     if ( WinSendMsg( WinWindowFromID(hwnd,DID_SENSITIVE),
                                      BM_QUERYCHECK,
                                      NULL,
                                      NULL))
                     {

                         ulSearchFlags |= MLFSEARCH_CASESENSITIVE;
                     }


                     seMLESearchData.cb       = sizeof( seMLESearchData );

                     seMLESearchData.pchFind  = auchFindText;
                     seMLESearchData.cchFind  = strlen( auchFindText );


                 /************************************************************
                     The search always starts at the cursor point and
                     continues to the end of the MLE.  It would be better
                     to put a WRAP box in the Find dialog box so the search
                     would wrap around to the beginning if the user so desired
                     Currently the user has to <ctrl> HOME to get to the top
                     of the MLE before doing a find/Change all once the
                     cursor is at the bottom of the MLE.
                 *************************************************************


                     seMLESearchData.iptStart = -1;   /* Start at cursor IPT
                     seMLESearchData.iptStop  = -1;   /* Search to end of MLE


                 /* Is there some text to search for ? */

                     if ( seMLESearchData.cchFind )
                     {

                     /********************************************************
                         Since a WinSendMsg is done here instead of a WinPost
                         then the search will be synchronous.  The
                         application will wait until the search is complete
                         before continuing.  This is ok in this application
                         since we are limiting the search.  The MLE sends
                         notification messages occasionally when it is searchi
                         so the the user can have the option of aborting the
                         search.
                     *********************************************************


                         if ( !WinSendMsg( hwndMLE,
                                           MLM_SEARCH,
                                           (MPARAM) ulSearchFlags,
                                           (MPARAM) &seMLESearchData ) )
                         {

                             TEDDisplayErrorID( SID_CANT_FIND );

                         }
                         else
                         {

                             WinSetFocus( HWND_DESKTOP, hwndMLE );

                         }

                     }

                     break;

                  case MBID_CANCEL:   /* modeless so don't no need to dismiss

                     WinPostMsg( hwnd, WM_CLOSE, NULL, NULL );
                     break;

                  default:

                     return( WinDefDlgProc( hwnd, msg, mp1, mp2 ));
                     break;

             } /* End switch (SHORT1FROMMP(mp1)) */
             break;

             return (MRESULT) FALSE;

         default:
             return( WinDefDlgProc( hwnd, msg, mp1, mp2 ));
             break;

     } /* End switch ( msg ) */

     return (MRESULT) FALSE ;

 }




 /**********************************************************************

     MLESaveFile - This procedure enables the save file dialog, lets
                   the user choose a file, then saves the contents of
                   the MLE to that file.

 **********************************************************************/



 VOID MLESaveFile( HWND hwnd, PSZ pszFname, PSZ pszFullName )
 {

     BOOL    fSuccess = TRUE;     /* Flag if user want to save               *
     DLF            dlf;                 /* Data structure for save dialog
     HFILE   hf;                  /* File handle of file to save MLE in      *


 /****************************************************************************
     Initialize the data structure used by the save as toolkit dialog.
     Call DlgFile which will put up the save as dialog and return back
     a handle to the opened file.
 *****************************************************************************


     SetupDLF( &dlf,                      /* Pointer to dialog data structure
               DLG_SAVEDLG | DLG_HELP,    /* Type of dialog to bring up
               &hf,                       /* Pointer to file handle
               "\\*.DOC",                 /* Mask of files to bring up
               "Dialog Error",            /* Msgbox title if Error
               "Save as",                 /* Title of Save as dialog window
               "No Help Available");      /* Help message


     dlf.szFileName[0] = dlf.szOpenFile[0] = '\000' ;



     switch( DlgFile( hwnd, &dlf ))
     {

         case TDF_ERRMEM:
         case TDF_INVALID:
         case TDF_NOOPEN:

             fSuccess = FALSE;
             break;

         case TDF_NOSAVE:

             fSuccess = FALSE;
             break;

         default:

             break;

     }

 /****************************************************************************
     If fSuccess is TRUE then the user chose a file and we have the handle
     to it.  So we save the contents of the MLE to that file handle and update
     the file name information.
 *****************************************************************************


     if ( fSuccess )
     {

         if ( MLESaveToFile( hf ))
         {

         /* Copy the file info into the correct place */

             strcpy( pszFname, dlf.szFileName );
             strcpy( pszFullName, dlf.szOpenFile );

         }

         DosClose( hf );


     }

 }

 /****************************************************************************

     MLESaveToFile - This procedure takes a passed file handle and writes
                     the contents of the MLE to it.

           Returns - True if the MLE was written to the file handle
                     succesfully otherwise FALSE is returned.

 *****************************************************************************

 BOOL MLESaveToFile( HFILE hf )
 {

     PCHAR   pvBuff;              /* Pointer to buffer with exported MLE data*
     IPT            iptFormatedTextSize; /* The number of bytes of FORMATTED d
     IPT     iptTextSize;         /* The number of bytes in the MLE          *
                                  /* NOTE!!! not equal to iptFormatedTextSize*

     IPT            iptData;             /* Temp used to hold IPT data
     USHORT  cbBytesWritten;      /* Number of bytes written from DosWrite   *
     BOOL    bRetVal = TRUE;      /* Default to OK                           *


     /************************************************************************
         First we query the MLE to find out how much data is it. The
         reason we ask for it with the QUERYFORMATTEXTLENGTH is because
         the MLE stores the text in a different format than it exports it.
         Mainly because it stores hard returns as one byte (LF) although
         based on the formatting currently enabled it may export the LF as
         a CR/LF in which case asking the MLE how much data it has with the
         QUERYTEXTLENGTH message would yield a number too small.

         In essence we are asking the MLE :

              "If the data were exported at this time how many bytes would
               be required to store it??"
     *************************************************************************


         iptFormatedTextSize = (IPT) WinSendMsg( hwndMLE,
                                                 MLM_QUERYFORMATTEXTLENGTH,
                                                 (MPARAM) (IPT) 0L,  /*beg MLE
                                                 (MPARAM) (IPT) -1 );/*end MLE



     /* Now Query MLE to find out how much data there is (IPT format) */

         iptTextSize = (IPT) WinSendMsg( hwndMLE,
                                         MLM_QUERYTEXTLENGTH,
                                         NULL,
                                         NULL );


     /* Allocate a buffer based on the formated size */

     /*********************************************************************
        The "+ (IPT) 1" is due to a bug in the MLE that should have been
        corrected.  This plus one will not effect the sample app once
        the bug is fixed
     *********************************************************************/


         if ( MyAllocMem( &pvBuff, iptFormatedTextSize + (IPT) 1, 0 ) ) {


             TEDDisplayErrorID( SID_CANT_ALLOCATE_MEMORY );

             bRetVal = FALSE;

         }
         else
         {

         /* Set up IMPORT/EXPORT transfer location */

             WinSendMsg( hwndMLE,
                         MLM_SETIMPORTEXPORT,
                         (MPARAM) pvBuff,                 /* Pointer to buff *
                         (MPARAM) iptFormatedTextSize );  /* Size of buff    *



         /* Now do the Export into the allocated BUFFER */


             iptData = 0L;

             WinSendMsg( hwndMLE,
                         MLM_EXPORT,                /* EXPORT TYPE
                         (MPARAM) &iptData,         /* IPT start for EXPORT
                         (MPARAM) &iptTextSize ) ;  /* Amount to EXPORT (bytes


         /********************************************************************
             iptTextSize is decremented by the MLE based on how many bytes
             were actually exported.  If this number is not 0 then an incorrec
             amount of data was exported.
         *********************************************************************


             if ( iptTextSize != (IPT) 0L )
             {
                 TEDDisplayErrorID( SID_UNABLE_TO_EXPORT );
                 bRetVal = FALSE;
             }


         /********************************************************************
             Since we might of been shrinking the size of the file we need to
             reset the size of the file to 0. This way the size of the file
             becomes the number of bytes that we will write to it.
         *********************************************************************

             if ( bRetVal && DosNewSize( hf, 0L ) )
             {
                 TEDDisplayErrorID( SID_UNABLE_TO_SET_NEWSIZE );
                 bRetVal = FALSE;
             }


         /********************************************************************
            Note iptFormatedTextSize is not checked since we limit the
            size of the MLE to something that will fit in a USHORT.
         *********************************************************************

               if ( bRetVal && DosWrite( hf,
                                       (PVOID) pvBuff,
                                       (USHORT) iptFormatedTextSize,
                                       &cbBytesWritten ) )
             {

                 TEDDisplayErrorID( SID_DOSWRITE_FAILED );
                 bRetVal = FALSE;

             }

         /* Free the buffer we used for the transfer */

             MyFreeMem( pvBuff );


                if ( bRetVal && ( iptFormatedTextSize != (IPT) cbBytesWritten
             {
                 TEDDisplayErrorID( SID_WRONG_BYTE_COUNT );
                 bRetVal = FALSE;
             }





         } /* End if Buffer available */

     return ( bRetVal );   /* return success flag */

 }


 /****************************************************************************

     MLEOpenFile - This procedure prompts the user for a file name to edit
                   using the open file dialog supplied with the toolkit.
                   If a user chooses a file to open then the file name and
                   fully qualified path are copied into the passed parameters.

 *****************************************************************************

 VOID MLEOpenFile( HWND hwnd, PSZ pszFname, PSZ pszFullName )
 {

     BOOL       fSuccess = TRUE;  /* Success flag for file chosen by user */
     DLF               dlf;              /* Data structure for Open dialog
     HFILE      hf;               /* File handle of file chosen by user   */
     PCHAR      pvBuff;           /* Buffer user to import file into MLE  */
     USHORT     cbBytesRead;      /* Number of bytes read by DosRead      */
     FILESTATUS stsInfo;          /* Information about the file           */
     IPT               iptData;          /* Position in MLE to import file


     SetupDLF( &dlf,
               DLG_OPENDLG | DLG_HELP,
               &hf,
               "\\*.DOC",
               "Dialog Error",            /* Msgbox title if Error
               "Open",
               "No help available");


     dlf.szFileName[0] = dlf.szOpenFile[0] = '\0' ;



     switch( DlgFile( hwnd, &dlf ))
     {

         case TDF_ERRMEM:
         case TDF_INVALID:

            fSuccess = FALSE;
            break;

         case TDF_NOOPEN:

            fSuccess = FALSE;
            break;

         default:

            break;
     }



     if ( fSuccess ) {


     /* Find out the size of the file */

         if ( DosQFileInfo( hf,
                            FIL_STANDARD,
                            (PBYTE) &stsInfo,
                            (USHORT) sizeof( FILESTATUS )))
         {

             TEDDisplayErrorID( SID_DOSQFILEINFO_FAILED );

         }


     /* If file is too large then truncate it */

         if ( stsInfo.cbFile > (LONG) TXT_LIMIT )
         {

             TEDDisplayErrorID( SID_FILE_TOO_LARGE );

             stsInfo.cbFile = (LONG) TXT_LIMIT;

         }


     /* Allocate a buffer to do the IMPORT */

         if (  MyAllocMem( (PVOID *) &pvBuff,
                           (LONG) stsInfo.cbFile,
                           0    ))
         {

             TEDDisplayErrorID( SID_CANT_ALLOCATE_MEMORY );

         }
         else
         {


         /*****************************************************************
            stsInfo.cbFile is always truncated to TXT_LIMIT so it will
            always fit in a short.
         *****************************************************************/

             if ( !DosRead( hf,
                                  (PVOID) pvBuff ,
                            (USHORT) stsInfo.cbFile,
                            &cbBytesRead )) {


                 MLEDisable( hwndMLE );



             /* Delete whatever is in MLE right now */


                 MLEDeleteContents( hwndMLE );


             /* Reset the undo since a new file was imported into the MLE */

                 WinSendMsg( hwndMLE, MLM_RESETUNDO, NULL, NULL );



             /* Read the data succesfully, now import to MLE */

                 WinSendMsg( hwndMLE,
                             MLM_SETIMPORTEXPORT,
                             (MPARAM) pvBuff,
                             (MPARAM) cbBytesRead );

                 iptData = 0L;

                    WinSendMsg( hwndMLE,
                             MLM_IMPORT,
                             (MPARAM) &iptData,
                             (MPARAM) cbBytesRead );

             /****************************************************************
                 Here we should check to see if the number of bytes imported
                 matches the number of bytes we told the MLE to import.
             *****************************************************************


             /* Release memory */

                 MyFreeMem( pvBuff );


             /* Copy the file name info into the correct place */

                 strcpy( pszFname, dlf.szFileName );
                 strcpy( pszFullName, dlf.szOpenFile );


                 MLEEnable( hwndMLE );


             }
             else
             {
                 TEDDisplayErrorID( SID_DOS_READ_FAILED );

             }

         }

         DosClose( hf );

     }

 }



 /****************************************************************************

     MLEInit - This procedure initializes certain MLE parameters to a known
               state.

 ****************************************************************************/


 VOID MLEInit( HWND hwnd )
 {

     /************************************************************************
         Set the text limit of the MLE to TXT_LIMIT. Any attempt to exceed
         this limit will result in a WM_CONTROL message (notification)
         to the MLE.
     *************************************************************************

         WinSendMsg( hwnd,
                     MLM_SETTEXTLIMIT,
                     (MPARAM) ( (LONG) TXT_LIMIT),
                     NULL );


     /************************************************************************
         Set the MLE format to MLFIE_CFTEXT which is the Cliboard text format.
         This format uses CR/LF for line delineation on export and recognizes
         either LF, CR/LF, or LF/CR as a line delineation on import.
     *************************************************************************

         WinSendMsg( hwnd,
                     MLM_FORMAT,
                     (MPARAM) (USHORT) MLFIE_CFTEXT,
                     NULL );


 }


 /****************************************************************************

     MLESetWrap - This procedure determines the current set of the WRAP state
                  in the MLE and optionally (fToggle TRUE ) toggles it. In
                  either case the Menu item Word wrap is checked or unchecked
                  in order to display the correct wrap state of the MLE.

 *****************************************************************************



 VOID MLESetWrap( HWND hwnd, HWND hwndMenuWrap, BOOL fToggle )
 {
     BOOL fWrap;         /* The wrap state of the MLE */


     /* First query the wrap from the MLE */

     fWrap = (BOOL)SHORT1FROMMR( WinSendMsg( hwnd, MLM_QUERYWRAP, NULL, NULL )


     if ( fToggle )
     {

         fWrap = !fWrap;

         if ( !WinSendMsg( hwnd, MLM_SETWRAP, (MPARAM) fWrap, NULL ) )
         {
             TEDDisplayErrorID( SID_CANT_SET_WRAP );

             return;
         }

     }


     /* Show the checked state on the menu item */

     WinSendMsg(hwndMenuWrap,
                MM_SETITEMATTR,
                MPFROM2SHORT(IDM_SETWRAP, TRUE),
                MPFROM2SHORT(MIA_CHECKED,
                fWrap ? MIA_CHECKED : FALSE) ) ;


 }




 /****************************************************************************

     MLEEnable - This procedure Enables the MLE so it will take input from the
                 user and update itself to correctly track what is in the
                 MLE buffer.

 *****************************************************************************

 VOID MLEEnable( HWND hwnd )
 {
     WinSendMsg( hwnd, MLM_ENABLEREFRESH, NULL, NULL );
 }






 /****************************************************************************

     MLEDisable - This procedure disables the MLE so it will not take input
                  from the user, change its pointer to the system wait icon
                  and not update itself.

 *****************************************************************************

 VOID MLEDisable( HWND hwnd )
 {
     WinSendMsg( hwnd, MLM_DISABLEREFRESH, NULL, NULL );
 }




 /****************************************************************************

     MLEDeleteContents - This procedure deletes all the text in the MLE.

       Returns - TRUE if succesfull.

 *****************************************************************************


 BOOL MLEDeleteContents( HWND hwnd )
 {

     IPT iptTextLength;      /* Length of the text currently in the MLE */



     iptTextLength = (IPT) WinSendMsg( hwnd, MLM_QUERYTEXTLENGTH, NULL, NULL )


 /* Now delete all the text in the MLE */

     return (  (IPT) WinSendMsg( hwnd,
                                 MLM_DELETE,       /* Delete MSG */
                                 (MPARAM) 0,       /* Start IPT location */
                                 (MPARAM) iptTextLength ) == iptTextLength );



 }

 /****************************************************************************

     TEDDisplayErrorID - This procedure loads the text associated with the
                         passed in resource identifier and displays it to
                         the user in the form of a message box.

               Returns - TRUE if the user clicked on okay

 *****************************************************************************


 BOOL TEDDisplayErrorID( USHORT usid )
 {

     UCHAR  uchTempBuff[ MAX_STRING_SIZE ]; /* Temp used to load string into*/
     USHORT usRetVal;                       /* Return val */


     if ( WinLoadString( hab, (HMODULE) NULL, usid, sizeof( uchTempBuff ), uch
     {

        usRetVal = MESSAGE( uchTempBuff );

     }
     else
     {

        usRetVal = MESSAGE("Cannot load resource string");

     }

     return( usRetVal == MBID_OK );
 }


 /****************************************************************************

     TEDInitDlgSysMenu - This procedure takes the system menu from the passed
                         window handle and deletes the :

                                 MINIMIZE
                                 MAXIMIZE
                                 SIZE
                                 RESTORE
                                 TASKMANAGER

                         entries from the system menu.

 *****************************************************************************


 VOID TEDInitDlgSysMenu( HWND hDlg )
 {
     HWND hSysMenu;      /* Window handle of the system menu attached to the *
                         /* passed dialog handle                             *




     hSysMenu=WinWindowFromID(hDlg, FID_SYSMENU);

     WinSendMsg(hSysMenu,
                MM_DELETEITEM,
                MPFROM2SHORT(SC_MINIMIZE,TRUE),
                MPFROMSHORT(NULL));

     WinSendMsg(hSysMenu,
                MM_DELETEITEM,
                MPFROM2SHORT(SC_MAXIMIZE,TRUE),
                MPFROMSHORT(NULL));

     WinSendMsg(hSysMenu,
                MM_DELETEITEM,
                MPFROM2SHORT(SC_SIZE,TRUE),
                MPFROMSHORT(NULL));

     WinSendMsg(hSysMenu,
                MM_DELETEITEM,
                MPFROM2SHORT(SC_RESTORE,TRUE),
                MPFROMSHORT(NULL));

     WinSendMsg(hSysMenu,
                MM_DELETEITEM,
                MPFROM2SHORT(SC_TASKMANAGER,TRUE),
                MPFROMSHORT(NULL));


 }


 /***************************************************************************

     TEDCleanupExit - This procedure cleans up by determining if valid
                      handles existed and then destroying them.

                      NOTE!! This procedure calls DosExit and thus never
                             returns.

 ***************************************************************************/


 VOID TEDCleanupExit(VOID)
 {


 /* If the find dialog was ever used then destroy it */

     if ( hwndDlgFind != (HWND) NULL )
     {
         WinDestroyWindow( hwndDlgFind );
     }




 /*  Standard Presentation Manager Application exit sequence. */


     if ( hwndFrame != (HWND) NULL )
     {
         WinDestroyWindow (hwndFrame ) ;
     }


     if ( hmq != (HMQ) NULL )
     {
         WinDestroyMsgQueue (hmq) ;
     }


     if ( hab != (HAB) NULL )
     {
         WinTerminate (hab) ;
     }


     DosExit( EXIT_PROCESS, 0 );


 }


 /****************************************************************************

     TEDUpdateTitleBar - This function updates the title (FID_TITLEBAR) of the
                         passed in hwnd with the two passed in string.
                         The strings are format as  <STRING> - <STRING>.
                         This is used to display the program name and file
                         currently being edited.

 *****************************************************************************



 VOID TEDUpdateTitleBar( HWND hwnd, PSZ pszPgmName, PSZ pszFileName )
 {

     UCHAR auchTemp [ 2 * CCHMAXPATH ];
     HWND hwndTitle;


     hwndTitle = WinWindowFromID( hwnd , FID_TITLEBAR);


     sprintf( auchTemp, "%s - %s", pszPgmName, pszFileName );

     WinSetWindowText(hwndTitle, auchTemp );


 }


 TEDHELP.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TED\TEDHELP.C

 /**************************************************************************

     Tedhelp.c

     Created by Microsoft Corporation, IBM Corporation 1989

 ---------------------------------------------------------------------------


         This file contains all the helper procedures required to
         include Help Manager in an application. The normal
         procedure is to:


             Define the .itl file which has all the help panel
             text in it.

             Define the Help tables and sub tables.  This maps
             help panel id numbers to menu id numbers so help
             manager knows which contectual help to put up
             when a user requestes it.

             Create a help instance.

             Associate a help instance with the current window
             chain.  As long as each new window created is
             part of the window chain (parent or owner) then
             there is no need to associate a help instance with
             each new window.




     Modification history

             091189  -  James Bratsanos, created


 ***************************************************************************/


 /* Define Constants for additional includes  */


 #define INCL_BASE
 #define INCL_WIN



 #include <os2.h>
 #include <string.h>
 #include "ted.h"
 #include "tedhelp.h"
 #include "tedhp.h"
 #include "teddlg.h"




 HWND hwndHelpInstance = (HWND) NULL;        /* The handle to the the Help */
                                             /* Manager help instance      */

 /****************************************************************************

     TEDTerminateHelp - This procedure disassociates the help instance
                        attached to the passed in window handle, and then
                        destroys it.

 ****************************************************************************/


 VOID APIENTRY TEDTerminateHelp( HWND hwnd )
 {


     if ( hwndHelpInstance != (HWND) NULL )
     {

     /* Dissasociate the Help instance */

         WinAssociateHelpInstance( NULL, hwnd );

     /* Destroy the HELP instance */

         WinDestroyHelpInstance( hwndHelpInstance );
     }

 }


 /****************************************************************************

     TEDInitHelp - This procedure opens the defined help library for this
                   application then creates a help instance and associates
                   it with the passed in window handle (which is the frame)

 ****************************************************************************/


 USHORT APIENTRY TEDInitHelp( HWND hFrame, HAB hab )
 {
     HELPINIT stHMInit;                  /* Help Manager init structure
     BOOL     bHelpLibFound=TRUE;        /* Help library found flag

 /***************************************************************************
     If the help tables were arrays instead of resources they would be
     set up as static arrays at this point in the procedure.
 ***************************************************************************/


 /***************************************************************************
     The help manager will search for the help library names first in
     the HELP environment variable and then in the current directory.

     NOTE: To specify multiple library names seperate the names with
           a blank.  Note the library names must include the extension
 ***************************************************************************/



 /************************************************************************

     Initialize Help Manager init  structure

 ************************************************************************/

     stHMInit.cb                        = sizeof(HELPINIT);
     stHMInit.ulReturnCode              = 0L;
     stHMInit.pszTutorialName           = NULL;
     stHMInit.phtHelpTable              = (PHELPTABLE) (0xffff0000 |
                                                       (LONG) TED_HELP_TABLE )
     stHMInit.hmodHelpTableModule       = (HMODULE) NULL;
     stHMInit.hmodAccelActionBarModule  = (HMODULE) NULL;
     stHMInit.idAccelTable              = 0;
     stHMInit.idActionBar               = 0;


     stHMInit.pszHelpWindowTitle        = "Ted Editor Help";
     stHMInit.usShowPanelId             = CMIC_HIDE_PANEL_ID;
     stHMInit.pszHelpLibraryName        = HELP_LIBRARY;



 /* Create Help Manager Object Window */


 /************************************************************************
     NOTE to Windows Programmers: This hwndHelpInstance is simply
                                  a window handle and not an INSTANCE
                                  handle as defined in Windows.
 ************************************************************************/


     if ( !( hwndHelpInstance = WinCreateHelpInstance( hab, &stHMInit )) )
     {


         if ( stHMInit.ulReturnCode == HMERR_OPEN_LIB_FILE )
         {

            TEDDisplayErrorID( SID_HERR_NO_LIBRARY );

         }
         else
         {

            TEDDisplayErrorID( SID_HELP_CANT_CREATE_INST );

         }

         return( FALSE );                /* cleanup handled at exit */

     }


 /*  Associate an instance of the Help Manager with the window chain. */



     if (!WinAssociateHelpInstance( hwndHelpInstance, hFrame ))
     {

         TEDDisplayErrorID( SID_HELP_CANT_ASSOC_INST );
         return( FALSE );                  /* cleanup handled at exit */

     }


 /* Check for any other initialization error from Help Manager */


     if(stHMInit.ulReturnCode)
     {

         TEDDisplayErrorID( SID_HELP_HLP_ERROR );

         return(FALSE );              /* exit if help initialization failed  *

     }
     else
     {
         return(HELP_OK);
     }


 }                                                       /* return to main */



 /***************************************************************************

     TEDSendHelpMsg - This procedure sends a message to the help manager

 ***************************************************************************/


 VOID APIENTRY TEDSendHelpMsg( USHORT usMsg )
 {

     WinSendMsg( hwndHelpInstance, usMsg, NULL, NULL );
 }




 /***************************************************************************

     TEDAssociateHelp - This procedure associates a help instance with the
                        passed in window handle.

                        NOTE: As long as a new window is part of the original
                              window chain , where each succesive window had
                              as its owner a handle that was associated with
                              the help instance the there is no need to associ
                              a help instance with this newly created window
                              since this new window will inherit the instance
                              because it is part of the chain.

 ***************************************************************************/


 VOID APIENTRY TEDAssociateHelp( HWND hwnd)
 {
     WinAssociateHelpInstance( hwndHelpInstance, hwnd );
 }



 /***************************************************************************

     TEDProcessHelpMsg - This procedure determines if the Help Manager
                         error is something that can be acted on and
                         acts accordingly.

               Return  - TRUE if it handled the message. False otherwise

 ***************************************************************************/

 BOOL APIENTRY TEDProcessHelpMsg(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
                         MRESULT *mresult)
 {

     switch( msg )
     {

     /************************************************************************

         In this case user pressed keys help and HELP manager sent a message
         notifying us. Help Manager expects the help panel id # in the .ITL
         file be returned so it can display that help panel in the help window

     *************************************************************************

         case HM_QUERY_KEYS_HELP:

             *mresult=MRFROMSHORT(KEYS_HELP_HP);
             break;

         case HM_HELPSUBITEM_NOT_FOUND:
         case HM_EXT_HELP_UNDEFINED:
         case HM_ERROR:

             *mresult = TEDHelpError(hwnd,msg,mp1,mp2);
             break;

         default:

             return (FALSE);
             break;

     }

     return (TRUE);
 }




 MRESULT APIENTRY TEDHelpError( HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
 {
   USHORT  SubTopicID;
   USHORT  ErrorCode;


 /*  Process the Help Manager message  */

     switch (msg)
     {
         case HM_HELPSUBITEM_NOT_FOUND:

             SubTopicID = HIUSHORT(mp2);


             switch (SubTopicID)
             {
                 case FID_CLIENT:         /* Edit area has input focus */
              /*************************************************/
              /* Returning false to HM in this case causes the   */
              /* extended help panel GENERAL_HP to display.      */
              /* Doing it this way instead of putting FID_CLIENT */
              /* in SubTbl_MAIN_MENU causes the Extended help    */
              /* item on the help panel's pull down to be        */
              /* grayed out, which is the desired result.        */
              /*************************************************/
                     return((MRESULT)FALSE);
                     break;

                 default:
                     break;
             }

             break;

         case HM_EXT_HELP_UNDEFINED:

             break;

         case HM_ERROR:

             ErrorCode = SHORT1FROMMP(mp1);



     /* Load the correct error message                 */



             switch (ErrorCode)
             {
                 case HMERR_NO_MEMORY:
                 case HMERR_ALLOCATE_SEGMENT:
                 case HMERR_FREE_MEMORY:

                     TEDDisplayErrorID( SID_HERR_MEMORY );
                     break;

                 case HMERR_DATABASE_NOT_OPEN:
                 case HMERR_OPEN_LIB_FILE:
                 case HMERR_READ_LIB_FILE:
                 case HMERR_CLOSE_LIB_FILE:
                 case HMERR_INVALID_LIB_FILE:

                     TEDDisplayErrorID( SID_HERR_LIB_ERROR );
                     break;

                 case HMERR_HELPITEM_NOT_FOUND:
                 case HMERR_HELPSUBITEM_NOT_FOUND:

                     return((MRESULT)TRUE);
                     break;

                 default:

                     TEDDisplayErrorID( SID_HERR_UNKNOWN );
                     break;


             }   /* End ErrorCode switch */


             break;  /* end - case HM_ERROR: */

         default:

             break;

     }  /* end the big switch */


     return((MRESULT)TRUE);

 }


 TEDMEM.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TED\TEDMEM.C

 /***************************************************************************

     Tedmem.c

     Created by Microsoft Corporation, IBM Corporation 1989

 ----------------------------------------------------------------------------

         This file contains the functions nessesary to allocate and free
         memory blocks.  It is implemented to return pointers and thus
         does not tie the application to Selector/Offsets etc.

 ***************************************************************************/


 #define INCL_DOS

 #include <os2.h>
 #include "tedmem.h"




 /***************************************************************************

     MyAllocMem - This procedure allocates a buffer of a requested length.

         Return - FALSE if succesful otherwise TRUE

 ***************************************************************************/


 APIRET APIENTRY MyAllocMem( PVOID *ppch,    /* Pointer to memory        */
                             ULONG usBytes,  /* Number of bytes to alloc */
                             ULONG ulDum1  ) /* Dummy                    */
 {

     APIRET apiRetVal;                       /* Return Value (FALSE = success*
     PCHAR pchTemp;                          /* Temp ptr used to alloc mem   *


     if ( (apiRetVal = (APIRET) DosAllocSeg( (USHORT) usBytes,
                                             &SELECTOROF( pchTemp ),
                                             0 ) ) == (APIRET) 0 )

     {

     /* Success!! */

         OFFSETOF( pchTemp ) = 0;            /* Set offset of selector to 0  *
         *ppch = pchTemp;                    /* Set passed in ptr to new mem *
     }


     return ( apiRetVal );

 }



 /***************************************************************************

     MyFreeMem - This procedure frees a buffer that was previously allocated
                 with MyAllocMem.

        Return - FALSE if succesful otherwise TRUE

 ***************************************************************************/


 APIRET APIENTRY MyFreeMem( PVOID pvTemp )
 {

     return ( APIRET) DosFreeSeg( SELECTOROF( pvTemp ) );

 }


 TERMINAL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\TERMINAL\TERMINAL.C

 /***
  *
  * TITLE
  *
  *        terminal.c
  *        Created by Microsoft Corporation 1987
  *
  * DESCRIPTION
  *
  *         This program emulates an ANSI terminal device.
  *
  *         The device driver COM.SYS must be installed at IPL  (Initial Progr
  *         Load) by specifying the following in config.sys:
  *                device = COM01.SYS
  *
  *        To run this program, type the following to the MS OS/2 prompt:
  *
  *                terminal [filename]
  *
  *         If a filename is specified, the initialisation of the COM port and
  *         the MODEM will be performed as indicated in the file (creation of
  *         file is described in options.c). For more details on setting optio
  *        pertaining to the COM port and the MODEM, see options.c
  *
  *        To exit the terminal emulator, type ALT F1.
  *
  ***/

 #define INCL_DOSSIGNALS
 #define INCL_SUB
 #define INCL_DOSFILEMGR
 #define INCL_DOSPROCESS
 #define INCL_DOSDEVICES

 #include        <os2def.h>
 #include        <bse.h>
 #include        <stdio.h>
 #include        <malloc.h>
 #include        <string.h>
 #include         <memory.h>
 #include         <conio.h>
 #include         "term.h"

 extern void get_options(int, char *[]);   /* get com port and modem options *
 extern void get_com_options(structComOptions *); /* get com options */
 extern make_modem_conn(void);                  /* make modem connection */
 extern modem(void);       /* returns TRUE if modem connection was requested *
 extern void discon_modem(void);           /* disconnect modem */

 void init_com_port(void);                  /* initialise com port */
 void far read_com_port(void);                  /* routine addr for a thread *
 void write_com_port(void);                  /* routine addr for another threa
 void APIENTRY handle_signals(USHORT,USHORT); /* BREAK signals handler */
 void close_conn(void);                          /*close modem connection & co
 void far xit(void);                        /* exit routine for this program *

 char *ErrMsg[] = {
                 "VIOGETCONFIG",
                 "KBDSETSTATUS",
                        "OUT OF MEMORY",
                 "DOSCREATETHREAD",
                 "DOSCLOSE COMPORT",
                 "DOSDEVIOCTL SETDCB",
                 "DOSREAD",
                 "VIOWRTTTY",
                 "DOSOPEN: check if COM driver is installed",
                 "DOSDEVIOCTL SETBAUD",
                 "DOSDEVIOCTL SETLINECHAR",
                 "DOSDEVIOCTL GETDCB",
                 "KBDCHARIN",
                 "DOSWRITE",
                 "VIOWRTCHARSTRATT",
                 "VIOWRTNCELL",
                 "DOSEXITLIST ADD_ADDR",
                 "VIOSETCURPOS",
                 "VIOGETMODE",
                 "DOSDEVIOCTL FLUSH_XMIT_RECV_QUEUE",
                 "DOSDEVIOCTL GETCOM",
                 "DOSSETSIGHANDLER",
                 "DOSDEVIOCTL SETBREAKON",
                 "DOSDEVIOCTL SETBREAKOFF",
                        "INVALID SIGNAL",
                        "do_option: invalid option type",
                        "mod_option: invalid option type",
                        "show_option: invalid option type"
 };
 HFILE                FileHndl = (HFILE) NULL;    /* COM port file handle */

 static char        ExitPgm = FALSE;    /* indicate if program should terminat
 static int        ErrorNumber = -1,   /* used as index to ErrMsg array */
                 ReturnCode  = -1;   /* retcode from system/subsystem call */




 /***        main - entry point to TERMINAL program
  *
  *        This routine obtains the com port and the modem options from the fi
  *      (specified in the command line) or directly from the user. It opens a
  *        initialises the com port. It sets up the modem connection (if one w
  *        requested). It creates a thread which loops continuously waiting to
  *        receive data from the com port and writing it out to the display. I
  *        loops continuosly waiting for an input from the keyboard and writin
  *        it out to the com port. Both the loops terminate when the user pres
  *        ALT-F1. It closes the modem connection (if one was made), and the c
  *        port and then terminates.
  *
  *        main(argc, argv)
  *
  *        ENTRY
  *                argc - number of command line arguments
  *                argv - pointer to an array of pointers (to command line arg
  *
  *        EXIT
  *                the program terminates
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 main(argc,argv)
 int         argc;
 char         *argv[];
 {
         UCHAR                *Stack1;             /* stack for a thread */
         TID                ThreadID;
         unsigned        RetCode,             /* return code */
                         Result = TRUE;
         USHORT                NumBytes;             /* number of bytes to be
         PFNSIGHANDLER        PrevAddress;
         USHORT                PrevAction;
         static KBDINFO OurKbdStatus =
                                    {sizeof(OurKbdStatus),KBD_BITMASK,};

         /* get COM port and modem options */
         get_options(argc, argv);

         /* open and initialise COM port */
         init_com_port();

         /* establish xit() as the exit routine */
         if ((RetCode = DosExitList(EXLST_ADD, (PFNEXITLIST)xit)) != 0)
           error(ERR_DOSEXITLIST, RetCode);

         /* make modem connection if requested */
         if (modem())
           Result = make_modem_conn();

         if (Result) {
           /* allocate memory for separate thread execution */
           if (!(Stack1 = (char *) _nmalloc(STACKSIZE)))
             error(ERR_OUTOFMEMORY, NO_RETCODE);

           /* create a thread that will execute the read_com_port() */
           Stack1 += STACKSIZE;
           if ((RetCode = DosCreateThread(read_com_port, &ThreadID,
                                          Stack1)) != 0)
             error(ERR_DOSCREATETHREAD, RetCode);

           /* set the keyboard status */
           if ((RetCode = KbdSetStatus(&OurKbdStatus,
                                       RESERVED)) != 0)
             error(ERR_KBDSETSTATUS, RetCode);

           /* set signal handler for BREAK signal */
           if ((RetCode = DosSetSigHandler(handle_signals,
                          &PrevAddress,
                          &PrevAction,
                          RECV_CTRL, BREAK)) != 0)
             error(ERR_DOSSETSIGHANDLER, RetCode);

           /* display "connected" message */
           printf("connected... \n");

           /* read chars from the keyboard and write to COM port */
           write_com_port();
         }        /* if (Result) */
 }




 /***        init_com_port - open the COM port and initialise line characteris
  *
  *        This routine opens the com port. It sets the com port options
  *        BaudRate, DataBits, Parity and the StopBits. It sets the read timeo
  *        It enables the automatic transmit and receive flow control.
  *
  *        init_com_port()
  *
  *        ENTRY
  *
  *        EXIT
  *                FileHndl = handle to com port
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void init_com_port()
 {
         USHORT                ActionTaken; /* action: file existed,created,re
         unsigned        RetCode;
         structLineChar         sLineChar;        /* line characteristics */
         structDCB        sDCB;                /* device control block informa
         structComOptions sComOptions;

         get_com_options(&sComOptions);

         /* open the com port */
         if ((RetCode = DosOpen(sComOptions.pPortName, &FileHndl,
                                &ActionTaken, 0L, 0, 0x0001, 0x0042, 0L)) != 0
           error(ERR_DOSOPEN, RetCode);

         /* set the baud rate */
         if ((RetCode = DosDevIOCtl(0L, &(sComOptions.iBaudRate),
                                    SETBAUD, SERIAL, FileHndl)) != 0)
           error(ERR_IOCTLSETBAUD, RetCode);

         /* set Data Bits, Stop Bits, Parity */
         sLineChar.DataBits = sComOptions.chDataBits;
         sLineChar.Parity   = sComOptions.chParity;
         sLineChar.StopBits = sComOptions.chStopBits;
         if ((RetCode = DosDevIOCtl(0L, &sLineChar, SETLINECHAR,
                                    SERIAL, FileHndl)) != 0)
           error(ERR_IOCTLSETLINECHAR, RetCode);

         /* get device control block info */
         if ((RetCode = DosDevIOCtl(&sDCB, 0L, GETDCB, SERIAL,
                                    FileHndl)) != 0)
           error(ERR_IOCTLGETDCB, RetCode);

         sDCB.Flags2 |= 0x03;        /* enable auto Xmit and recv flow control
         sDCB.Flags3 &= 0xf9;        /* clear read timeout flags */
         sDCB.Flags3 |= 0x04;        /* set wait for something read timeout */
         sDCB.ReadTimeOut = READTIMEOUT;        /* set read timout value */

         /* set device control block info */
         if ((RetCode = DosDevIOCtl(0L, &sDCB, SETDCB, SERIAL,
                                     FileHndl)) != 0)
           error(ERR_IOCTLSETDCB, RetCode);
 }




 /***        write_com_port        - read chars from the keyboard and write to
  *
  *        This routine loops continuosly waiting for a keyboard input and
  *        writing it out to the com port. The loop terminates when the user
  *        presses the ALT-F1.
  *
  *        write_com_port()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void write_com_port()
 {
         USHORT                NumBytes;        /* number of bytes actually wr
         unsigned        RetCode;
         char                OutBuffer;        /* output buffer */
         KBDKEYINFO        OurKeyData;        /* struc to read a char from kbd


         while (!ExitPgm) {
           /* read input from the keyboard */
           if ((RetCode = KbdCharIn(&OurKeyData,
                                    IOWAIT, RESERVED)) != 0)
             error(ERR_KBDCHARIN, RetCode);
           OutBuffer = OurKeyData.chChar;
           if ((OutBuffer == 0) || (OutBuffer == 0xE0)) {
             OutBuffer = OurKeyData.chScan;
             switch (OutBuffer) {
               case DEL_SCAN  : OutBuffer = DEL_ASCII;
                                break;
               case ALT_F1    : ExitPgm = TRUE;
                                break;
               default        : break;
             };
             if (OutBuffer != ALT_F1)
               /* write the input from the keyboard to the com port */
               if ((RetCode = DosWrite(FileHndl, &OutBuffer, 1,
                                       &NumBytes)) != 0)
                 error(ERR_DOSWRITE, RetCode);
           }
           else {
             /* write the input from the keyboard to the com port */
             if ((RetCode = DosWrite(FileHndl, &OutBuffer, 1,
                                     &NumBytes)) != 0)
               error(ERR_DOSWRITE, RetCode);
           };
         };
 }




 /***         read_com_port - read chars from com port and display on CRT scre
  *
  *         This routine is executed by a thread. It loops continuously waitin
  *      to receive data from the com port and writing it out to the display.
  *        The loop terminates when the user presses ALT-F1.
  *
  *        read_com_port()
  *
  *        ENTRY
  *                FileHndl (handle to com port) setup
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void far read_com_port()
 {
         USHORT                NumBytes;        /* number of bytes actually re
         unsigned        RetCode;
         struct CharsInQue {         /*data ret'ned by get num chars in que IO
                         unsigned         NumCharsInQue;
                         unsigned        SizeQue;
         } sCharsInQue;
         char                InBuffer[INBUFLENGTH];        /* input buffer  */

         while (!ExitPgm) {
           /* get number of characters in receive queue */
           if ((RetCode = DosDevIOCtl(&sCharsInQue, 0L, GETNUMCHARS,
                                      SERIAL, FileHndl)) != 0)
             setup_error_msg(ERR_IOCTLSETDCB, RetCode);

           if (sCharsInQue.NumCharsInQue == 0)
              sCharsInQue.NumCharsInQue++;

           if ((RetCode = DosRead(FileHndl, InBuffer, sCharsInQue.NumCharsInQu
                                  &NumBytes)) != 0)
             setup_error_msg(ERR_DOSREAD, RetCode);

           if (NumBytes) {
             /* write to the tty display */
             if ((RetCode = VioWrtTTY(InBuffer, NumBytes, RESERVED)) != 0)
               setup_error_msg(ERR_VIOWRTTTY, RetCode);
           }
         }
 }




 /***        handle_signals  - handle BREAK signal
  *
  *        This routine is the handler for the BREAK signal. When the user pre
  *        the BREAK key, this routine sends a BREAK on the com line.
  *
  *        handle_signals(SigArg, SigNumber)
  *
  *        ENTRY
  *                SigArg - not used
  *                SigNumber - signal number being processed
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *            Since this is a signal handler it must be declared FAR.
  *
  ***/

 void APIENTRY handle_signals(SigArg, SigNumber)
 USHORT        SigArg,
         SigNumber;
 {
         unsigned        RetCode,
                         Status;
         char                OutBuffer;

         switch (SigNumber) {
         case BREAK :/* send BREAK to the com port */
                     if ((RetCode = DosDevIOCtl(&Status, 0L,
                                        SETBREAKON, SERIAL, FileHndl)) != 0)
                       error(ERR_IOCTLSETBREAKON, RetCode);
                     DosSleep(1L);
                     if ((RetCode = DosDevIOCtl(&Status, 0L,
                                        SETBREAKOFF, SERIAL, FileHndl)) != 0)
                       error(ERR_IOCTLSETBREAKOFF, RetCode);
                     break;
         default    :
                     error(ERR_INVALIDSIGNAL, NO_RETCODE);
                     break;
         }
 }




 /***        close_conn - close modem connection and the com port
  *
  *        This routine closes the modem connection if one was made and then
  *        closes the com port.
  *
  *        close_conn()
  *
  *        ENTRY
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void close_conn()
 {
         int                RetCode;
         static char         CloseMsg[] = "exiting terminal...";

         /* send closing message to the display */
         if ((RetCode = VioWrtTTY(CloseMsg, sizeof(CloseMsg), RESERVED)) != 0)
           print_err_msg(ErrMsg[ERR_VIOWRTTTY], RetCode);

         /* if modem connection was made, close it */
         if (modem())
           discon_modem();

         /* close the com port */
         if ((RetCode = DosClose(FileHndl)) != 0)
           print_err_msg(ErrMsg[ERR_DOSCLOSECOMPORT], RetCode);
 }




 /***                ERROR HANDLING
  *
  *        There are two error handling routines:
  *                - error() which is invoked from the main thread
  *                - setup_err_msg() which is invoked from the thread executin
  *                  the read_com_port() routine
  *        The two routines perform different functions (described below). Thi
  *        is done so that if an error is encountered "simultaneously" in both
  *        the threads, there will be no race condition in error reporting.
  *
  *        The following routines are also part of the error handling:
  *                - xit() is a DosExitList routine
  *                - print_err_msg() does the actual printing of the error mes
  *
  *        All the routines discussed above are defined below.
  *
  ***/



 /***        error
  *
  *        This routine is invoked when there is an error. It prints an
  *        an error message and calls DosExit.
  *
  *        error(ErrNum, RetCode)
  *
  *        ENTRY
  *                ErrNum - error number (used as index to error message array
  *                RetCode - return code from a DOS system/subsystem call
  *
  *        EXIT
  *                global variables ErrorNumber & ReturnCode still contain the
  *                initial value of -1. At program termination (via DosExit ca
  *                the DosExitList routine xit() is invoked (described below).
  *                On return from xit(), this program will terminate.
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 error(ErrNum, RetCode)
 int  ErrNum;
 int  RetCode;
 {
         print_err_msg(ErrMsg[ErrNum], RetCode);
         DosExit(EXIT_PROCESS, 1);
 }




 /***        setup_error_msg - setup error message
  *
  *        Sets up the global ErrorNumber and ReturnCode. It then calls DosExi
  *
  *        setup_error_msg(ErrNum, RetCode)
  *
  *        ENTRY
  *                ErrNum - error number (used as index to error message array
  *                RetCode - return code from a DOS system/subsystem call
  *
  *        EXIT
  *                global variables ErrorNumber & ReturnCode are setup. At
  *                program termination (via DosExit call) the DosExitList
  *                routine xit() is invoked (described below). xit() will prin
  *                an error message. On return from xit(), this program will
  *                terminate.
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 setup_error_msg(ErrNum, RetCode)
 int        ErrNum,
         RetCode;
 {
         ErrorNumber = ErrNum;
         ReturnCode  = RetCode;
         DosExit(EXIT_PROCESS, 1);
 }




 /***        xit        - exit function executed at program termination
  *
  *        This is a DosExitList routine. It prints an error message if
  *        the global variable ErrorNumber is non-negative.
  *
  *        xit()
  *
  *        ENTRY
  *
  *        EXIT
  *                If ErrorNumber is non-negative, an error message is printed
  *                The program will then terminate.
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 void far xit(void)
 {
         if (ErrorNumber != -1)
           print_err_msg(ErrMsg[ErrorNumber], ReturnCode);
         close_conn();                 /* close modem connection and the com p
         DosExitList(XFER, 0L);
 }




 /***        print_err_msg - print error mesage
  *
  *        This routine prints an error message and the returncode.
  *
  *        print_err_msg(Msg, Retcode)
  *
  *        ENTRY
  *                Msg - error message string
  *                Retcode - returncode from DOS system/subsystem call
  *
  *        EXIT
  *
  *        WARNING
  *
  *        EFFECTS
  *
  ***/

 print_err_msg(Msg, RetCode)
 char        *Msg;
 int        RetCode;

 {
           printf("*** ERROR %s *** ", Msg);
          if (RetCode != -1)
           printf("ReturnCode = %d ", RetCode);
         printf("\n");
 }


 TEST.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\TEST.C

 #define INCL_WINCATCHTHROW
 #include "ddemlp.h"
 #include "stdio.h"

 int        APIENTRY DebugOutput( PCH );        /* private import */
 CATCHBUF catchbuf;

 void DumpItems(PULONG pul, USHORT c);
 void err(PSZ psz);
 void err2(PSZ psz, ULONG ul);

 typedef struct _ULLI {
     PLITEM next;
     ULONG ul;
 } ULLI;
 typedef ULLI FAR *PULLI;

 /*
  * exported for testing the DLL
  */
 void EXPENTRY DdeTest()
 {
 #if 0
     SEL sel;
     USHORT c;
     ULLI SchItem;
     PLST plst;
     PPILE ppile;
     HHEAP hheap;
     PLITEM pli;
     PBYTE pbytes;

     AssertF(FALSE, "Testing assertion failure");

     DebugOutput("\n\rList functions test\n\r");
     SemEnter();
     if (DosAllocSeg(2000, &sel, SEG_NONSHARED))
         err("DosAllocSeg failed");
     if (!(hheap = MyCreateHeap(sel, 0, 0, 0, 0, HEAPFLAGS)))
         err("CreateHeap failed");
     if (!(pbytes = FarAllocMem(hheap, 1000)))
         err("Could not allocate bytes");
     plst = NULL;
     SchItem.ul = 4L;
     SemLeave();
     DebugOutput("\n\rNULL list test....");
     SemEnter();
     DestroyLst(plst);
     FlushLst(plst);
     FindLstItem(plst, CmpULONG, (PLITEM)&SchItem);
     FindLstItem(plst, CmpULONG, NULL);
     FindLstItem(plst, NULL, (PLITEM)&SchItem);
     FindLstItem(plst, NULL, NULL);
     NewLstItem(plst, ILST_NOLINK);
     NewLstItem(plst, ILST_FIRST);
     NewLstItem(plst, ILST_LAST);
     PopLi(plst);
     RemoveLstItem(plst, (PLITEM)&SchItem);
     RemoveLstItem(plst, NULL);
     InsertLstItem(plst, (PLITEM)&SchItem, ILST_FIRST);
     SemLeave();
     DebugOutput("\n\rCreateList...");
     SemEnter();
     plst = CreateLst(hheap, sizeof(ULONG));
     if (!plst)
         err("CreateLst() failed");
     pli = NewLstItem(plst, ILST_FIRST);
     if (!pli)
         err("NewLstItem() failed");
     ((PULLI)pli)->ul = 1L;
     SchItem.ul = 1L;
     if (pli != FindLstItem(plst, CmpULONG, (PLITEM)&SchItem))
         err("FindLstItem() failed");
     if (pli != PopLi(plst)) {
         err("PopLi() failed");
     }
     if (FindLstItem(plst, NULL, NULL))
         err("unexpected success of FindLstItem()");
     for (SchItem.ul = 1L; SchItem.ul < 100L; SchItem.ul++) {
         if (!(pli = NewLstItem(plst, ILST_FIRST)))
             err("NewLstItem() failed");
         ((PULLI)pli)->ul = SchItem.ul;
         if (!FindLstItem(plst, CmpULONG, (PLITEM)&SchItem))
             err2("Item %ul not found", SchItem.ul);
     }
     SchItem.ul = 25L;
     if (!RemoveLstItem(plst, FindLstItem(plst, CmpULONG, (PLITEM)&SchItem)))
         err2("RemoveLstItem() failed on %ul", SchItem.ul);
     SchItem.ul = 47L;
     if (!RemoveLstItem(plst, FindLstItem(plst, CmpULONG, (PLITEM)&SchItem)))
         err2("RemoveLstItem() failed on %ul", SchItem.ul);
     SchItem.ul = 78L;
     if (!RemoveLstItem(plst, FindLstItem(plst, CmpULONG, (PLITEM)&SchItem)))
         err2("RemoveLstItem() failed on %ul", SchItem.ul);
     for (SchItem.ul = 1L; SchItem.ul < 100L; SchItem.ul++) {
         if (SchItem.ul == 25L || SchItem.ul == 47L || SchItem.ul == 78L) {
             if (FindLstItem(plst, CmpULONG, (PLITEM)&SchItem))
                 err2("Unexpected find of %ul", SchItem.ul);
         } else if (!FindLstItem(plst, CmpULONG, (PLITEM)&SchItem))
             err2("Unexpected failure to find %ul", SchItem.ul);
     }
     FlushLst(plst);
     if (PopLi(plst))
         err("Unexpected pass of PopLi()");
     DestroyLst(plst);
     plst = NULL;
     SemLeave();
     DebugOutput("\n\rList test passed.\n\r");


     DebugOutput("\n\rNULL Pile test...");
     SemEnter();
     ppile = NULL;
     DestroyPile(ppile);
     FlushPile(ppile);
     if (QPileItemCount(ppile))
         err("Unexpected pass of QPileItemCount()");
     if (CopyPileItems(ppile, pbytes))
         err("Unexpected pass of CopyPileItems()");
     if (FindPileItem(ppile, NULL, NULL, FPI_COUNT))
         err("Wrong count from FindPileItem()");
     if (FindPileItem(ppile, NULL, NULL, FPI_DELETE))
         err("Unexpected pass of FindPileItem()");
     if (AddPileItem(ppile, NULL, NULL))
         err("Unexpected pass of AddPileItem()");

     SemLeave();
     DebugOutput("\n\rPile test...");
     SemEnter();
     if (!(ppile = CreatePile(hheap, sizeof(ULONG), 8)))
         err("Unexpected failure of CreatePile()");
     for (SchItem.ul = 1L; SchItem.ul < 100; SchItem.ul++) {
         AddPileItem(ppile, (PBYTE)&SchItem.ul, CmpULONG);
         if (SchItem.ul > 25)
             if (AddPileItem(ppile, (PBYTE)&SchItem.ul, CmpULONG))
                 err("Unexpected TRUE(1) return by AddPileItem()");
         if (SchItem.ul > 50)
             if (AddPileItem(ppile, (PBYTE)&SchItem.ul, CmpULONG))
                 err("Unexpected TRUE(2) return by AddPileItem()");
         if (SchItem.ul > 75)
             if (AddPileItem(ppile, (PBYTE)&SchItem.ul, CmpULONG))
                 err("Unexpected TRUE(3) return by AddPileItem()");
     }
     if (QPileItemCount(ppile) != 99)
         err("Total pile count wrong");
     for (SchItem.ul = 1L; SchItem.ul < 100L; SchItem.ul++) {
         if (1 != (USHORT)FindPileItem(ppile, CmpULONG, (PBYTE)&SchItem.ul, FP
             err("Wrong count of items found by FindPileItem()");
     }
     for (SchItem.ul = 26L; SchItem.ul < 100L; SchItem.ul++) {
         AddPileItem(ppile, (PBYTE)&SchItem.ul, NULL);
         if (SchItem.ul > 50)
             if (!AddPileItem(ppile, (PBYTE)&SchItem.ul, NULL))
                 err("Unexpected FALSE(2) return by AddPileItem()");
         if (SchItem.ul > 75)
             if (!AddPileItem(ppile, (PBYTE)&SchItem.ul, NULL))
                 err("Unexpected FALSE(3) return by AddPileItem()");
     }
     for (SchItem.ul = 1L; SchItem.ul < 26L; SchItem.ul++) {
         c = (USHORT)FindPileItem(ppile, CmpULONG, (PBYTE)&SchItem.ul, FPI_COU
         if (c != 1)
             err("Wrong count of items found by FindPileItem()");
     }
     for (SchItem.ul = 26L; SchItem.ul < 51L; SchItem.ul++) {
         c = (USHORT)FindPileItem(ppile, CmpULONG, (PBYTE)&SchItem.ul, FPI_COU
         if (c != 2)
             err("Wrong count of items found by FindPileItem()");
     }
     for (SchItem.ul = 51L; SchItem.ul < 76L; SchItem.ul++) {
         c = (USHORT)FindPileItem(ppile, CmpULONG, (PBYTE)&SchItem.ul, FPI_COU
         if (c != 3)
             err("Wrong count of items found by FindPileItem()");
     }
     for (SchItem.ul = 76L; SchItem.ul < 100L; SchItem.ul++) {
         c = (USHORT)FindPileItem(ppile, CmpULONG, (PBYTE)&SchItem.ul, FPI_COU
         if (c != 4)
             err("Wrong count of items found by FindPileItem()");
     }
     if ((c = QPileItemCount(ppile)) != 99 + 74 + 49 + 24)
         err("Total pile count wrong");
     CopyPileItems(ppile, pbytes);
     DestroyPile(ppile);
     SemLeave();
     DumpItems((PULONG)pbytes, c);
     MyDestroyHeap(hheap);
 #endif
     return;
 }



 void DumpItems(pul, c)
 PULONG pul;
 USHORT c;
 {
     char sz[100];
     char far *psz;

     psz = sz;
     while (c--) {
         if ((c % 7) == 0) {
             *psz++ = '\n';
             *psz++ = '\r';
             *psz++ = '\0';
             DebugOutput(sz);
             psz = sz;
         }
         psz = ltoa(*pul, psz, &sz[99]);
         *psz++ = ' ';
         pul++;
     }
     *psz++ = '\n';
     *psz++ = '\r';
     *psz++ = '\0';
     DebugOutput(sz);
 }



 void err(psz)
 PSZ psz;
 {
     SemLeave();
     DebugOutput(psz);
 }

 void err2(psz, ul)
 PSZ psz;
 ULONG ul;
 {
     char sz[80];

     SemLeave();
     sz[0] = '\000';
     psz = lstrcat(sz, psz, &sz[79]);
     psz = ltoa(ul, psz, &sz[79]);
     DebugOutput(sz);
 }


 TESTSUBS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MONITOR\TESTSUBS.C

 /****************************** Module Header ******************************\
 * Module Name: TestSubs.C
 *
 * Collection of useful routines for test applications
 *
 * Created: 16-Jan-87
 *
 * Copyright (c) 1985, 1986, 1987  Microsoft Corporation
 *
 \***************************************************************************/

 #define INCL_WINHEAP
 #define INCL_WINWINDOWMGR
 #define INCL_WINRECTANGLES
 #define INCL_WINTIMER
 #define INCL_WINSYS
 #include <os2.h>
 #include <stdio.h>
 #include <string.h>
 #include "monitor.h"


 /***************************************************************************/

 extern HAB hab;

 /* StringWindow structure */
 typedef struct {
     int cchLine;
     int cLine;
     char *pchBuffer;
     char *pchBufferMax;
     char *pchBottomLine;
     char *pchOutput;
 } STRWND;

 int cyChar;                     /* Height of a line */
 int cyDescent;

 USHORT ticks;                     /* Tick count used with StartTime/StopTime

 HHEAP hhp = NULL;

 /***************************************************************************/

 /***************************** Public  Function ****************************\
 * BOOL InitTestSubs( )
 *
 * This routine MUST be called before using anything in this file.  Registers
 * window classes, loads brushes, etc.  Returns TRUE if successful, FALSE
 * otherwise.
 *
 * Warnings: Must be called AFTER the global variable NULL is initialized.
 *
 * History:
 *  19-Jan-87 by neilk  Created
 \***************************************************************************/

 BOOL InitTestSubs()
 {
     cyChar = 14;
     cyDescent = 2;

     if (!WinRegisterClass(hab, (PCH)"StringWindow", (PFNWP)StrWndProc,
        CS_SYNCPAINT, sizeof(STRWND *)))
         return(FALSE);

     hhp = WinCreateHeap(0, 0, 0, 0, 0, 0);

     return(TRUE);
 }


 void
 NextLine(
 STRWND *psw)
 {
     psw->pchBottomLine += psw->cchLine;
     if (psw->pchBottomLine == psw->pchBufferMax)
         psw->pchBottomLine = psw->pchBuffer;
     psw->pchOutput = psw->pchBottomLine;
     *psw->pchOutput = '\0';
 }


 /***************************** Public  Function ****************************\
 * VOID DrawString(hwnd, sz)
 *
 * This routine prints a string in the specified StringWindow class window.
 * sz is a near pointer to a zero-terminated string, which can be produced
 * with sprintf().
 *
 * History:
 *  19-Jan-87 by neilk  Created
 \***************************************************************************/

 VOID DrawString(hwnd, sz)
 HWND hwnd;
 char *sz;
 {
     register STRWND *psw;
     USHORT cLines = 0;

     psw = (STRWND *)WinQueryWindowUShort(hwnd, 0);
     NextLine(psw);
     cLines++;

     while (*sz) {
         switch (*sz) {
         case 0x0a:
             break;

         case 0x09:
             *psw->pchOutput++ = ' ';
             *psw->pchOutput++ = ' ';
             *psw->pchOutput++ = ' ';
             *psw->pchOutput++ = ' ';
             break;

         case 0x0d:
             *psw->pchOutput++ = '\0';
             NextLine(psw);
             cLines++;
             break;

         default:
             *psw->pchOutput++ = *sz;
         }
         sz++;
     }
     WinScrollWindow(hwnd, 0, cyChar * cLines, (PWRECT)NULL, (PWRECT)NULL, (HR
       (PWRECT)NULL, SW_INVALIDATERGN);
 }

 /***************************** Public  Function ****************************\
 * "StringWindow" window class
 *
 * Windows of the "StringWindow" window class are simple scrolling text output
 * windows that are refreshed properly as windows are rearranged.  A text buff
 * is maintained to store the characters as they are drawn.
 *
 * When creating a StringWindow window, lpCreateParams is actually a ULONG
 * containing the dimensions of the text buffer to be created, if 0L, then
 * a 80 by 25 buffer is created.
 *
 * History:
 *  19-Jan-87 by neilk  Created
 \***************************************************************************/

 MRESULT FAR PASCAL StrWndProc(hwnd, msg, mp1, mp2)
 register HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     register STRWND *psw;
     HPS hps;
     RECTL rclPaint;

     switch (msg) {
     case WM_CREATE:
         if (mp1     == 0L) {
             mp1     = MAKEULONG(80, 50);
         }
         if (!StrWndCreate(hwnd, SHORT1FROMMP(mp1), SHORT2FROMMP(mp1)))
             return(TRUE);
         break;

     case WM_DESTROY:
         if ((psw = (STRWND *)WinQueryWindowUShort(hwnd, 0)) != NULL) {
             WinFreeMem(hhp, (BYTE *)psw->pchBuffer, strlen(psw->pchBuffer));
             WinFreeMem(hhp, (BYTE *)psw, sizeof(STRWND));
         }
         break;

     case WM_PAINT:
         hps = WinBeginPaint(hwnd, (HPS)NULL, &rclPaint);
         PaintStrWnd(hwnd, hps, &rclPaint);
         WinEndPaint(hps);
         break;

     case WM_CALCVALIDRECTS:
 #ifdef LATER
         /*
          * We want to bottom right justify the bits.
          */
         (mp2)->yTop = (mp2)->yBottom -
                 (mp1)->yBottom;
 #endif
         break;

     default:
         return(WinDefWindowProc(hwnd, msg, mp1, mp2));
         break;
     }
     return(0L);
 }



 BOOL StrWndCreate(hwnd, cchLine, cLine)
 HWND hwnd;
 int cchLine, cLine;
 {
     register char *pch;
     char *pchEnd;
     STRWND *psw;

     if ((psw = (STRWND *)WinAllocMem(hhp, sizeof(STRWND))) == NULL)
         return(FALSE);
     /*
      * Allocate buffer for strings
      */
     if ((pch = (char *)WinAllocMem(hhp, cchLine * cLine)) == NULL) {
         WinFreeMem(hhp, (BYTE *)psw, sizeof(STRWND));
         return(FALSE);
     }

     psw->cchLine       = cchLine;
     psw->cLine         = cLine;
     psw->pchBuffer     = pch;
     psw->pchBufferMax  = pch + cchLine * cLine;
     psw->pchBottomLine = pch;
     psw->pchOutput     = pch;
     WinSetWindowUShort(hwnd, 0, (USHORT)psw);

     /*
      * Make all the lines empty
      */
     pchEnd = psw->pchBufferMax;
     while (pch != pchEnd) {
         *pch = '\0';
         pch += cchLine;
     }
     return(TRUE);
 }



 VOID PaintStrWnd(hwnd, hps, prcl)
 HWND hwnd;
 HPS hps;
 PRECTL prcl;
 {
     register STRWND *psw;
     register char *pch;
     int x;
     int y;
     WRECT rc;
     RECTL rcl;

     psw = (STRWND *)WinQueryWindowUShort(hwnd, 0);

     WinQueryWindowRect(hwnd, (PRECTL)&rc);

     WinFillRect(hps, (PRECTL)&rc, SYSCLR_WINDOW);

     x = rc.xLeft;
     y = rc.yBottom;
     pch = psw->pchBottomLine;

     if (prcl != NULL)
         WinIntersectRect(hab, (PRECTL)&rc, (PRECTL)&rc, prcl);

     do {
         if (y >= rc.yTop)
             break;
         if (y + cyChar >= rc.yBottom) {
             rcl.xLeft = x;
             rcl.yBottom = y + cyDescent;
             rcl.xRight = 1000;
             rcl.yTop = rcl.yBottom + cyChar;
             WinDrawText(hps, -1, (PSZ)pch, (PRECTL)&rcl, SYSCLR_WINDOWTEXT,
                     SYSCLR_WINDOW, 0);
         }
         y += cyChar;
         /*
          * Back up to previous line
          */
         if (pch == psw->pchBuffer)
             pch = psw->pchBufferMax;
         pch -= psw->cchLine;
     } while (pch != psw->pchBottomLine);
 }

 /***************************** Public  Function ****************************\
 * VOID StartTime()
 * VOID StopTime(hwndStrWnd, szMsg)
 *
 * These functions are used to time sections of code.  StartTime() begins the
 * timing, and StopTime() stops it, printing out the elapsed time since
 * StartTime() was called in the "StringWindow" class window hwndStrWnd, using
 * the sprintf() formatting string szMsg.  The elapsed time in milliseconds is
 * replaced for a "%d" in szMsg.
 *
 * History:
 *  19-Jan-87 by neilk  Created
 \***************************************************************************/

 VOID StartTime()
 {
     ticks = (USHORT)WinGetCurrentTime(hab);
 }

 VOID StopTime(hwndStrWnd, szFmt)
 HWND hwndStrWnd;
 char *szFmt;
 {
     USHORT dt;
     char rgch[80];

     dt = (USHORT)(WinGetCurrentTime(hab) - ticks);
     sprintf((char *)rgch, szFmt, dt);
     DrawString(hwndStrWnd, rgch);
 }


 THREADS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\COMTALK\THREADS.C

 /*
     threads.c -- Supplementary threads module
     Created by Microsoft Corporation, 1989
 */
 #define         INCL_DOSSEMAPHORES
 #define                INCL_DOSMEMMGR
 #define                INCL_DOSPROCESS
 #define                INCL_WINMESSAGEMGR
 #define                INCL_WINTRACKRECT
 #include        <os2.h>
 #include        "global.h"
 #include        "avio.h"
 #include        "circleq.h"
 #include        "comport.h"
 #include        "threads.h"
 #include        "malloc.h"

 int                rc;
 HWND                hWndMaster;
 BOOL                fNoUpdate = TRUE;
 BOOL                fWrap;
 /*
     Declare pointers to stacks
 */
 PINT                pStackWPT,
                 pStackRPT,
                 pStackWST;
 /*
     ...Selectors
 */
 SEL                selStackWPT,
                 selStackRPT,
                 selStackWST;
 /*
     ...Thread ID numbers
 */
 TID                tidWPT,
                 tidRPT,
                 tidWST;
 /*
     ...Control booleans, semaphores
 */
 BOOL                fBreak,                        /* Break active */
                 fAlive;                        /* Should the threads be kille
 LONG                lSemLock,                /* TypeAhead buffer locks... */
                 lSemEmpty,
                 lSemFull,
                 lSemOverflow;                /* Buffer overflow semaphore */
 CHAR                TypeAhead[BUFSIZE];        /* TypeAhead buffer and contro
 int                nBufLoc, nChars;
 LineInfo        aliReadAhead[RASIZE];
 int                cliReadAhead;
 LONG                lSemRALock;
 LONG                lSemRAFull;
 LONG                lSemRAEmpty;
 /*
     Messages....
 */
 char aszMessage[MBE_NUMMSGS][MAXLINELEN] = {
     "Error opening port",
     "Error writing port",
     "Error reading port",
     "Circular buffer overflowing"
 };
 /*
     Macros
 */
 #define        NOUPDATE                (MRESULT) FALSE
 #define        UPDATE                        (MRESULT) TRUE
 #define        ThdBufNextLoc(n)        (n = ((n + 1) % BUFSIZE))
 #define        ThdBufLastLoc(n)        (n = (n > 0) ? (n - 1) : (BUFSIZE - 1)
 #define        ThdNextRALoc(n)                (n = ((n + 1) % RASIZE))
 #define MessageBox(s, v)        WinPostMsg(hWndMaster, WM_MSGBOX, s, v)
 /*
     Local routines
 */
 void far WritePortThread(void);
 void far ReadPortThread(void);
 void far WriteScreenThread(void);
 void Process(Line, Line, int far *);

 int ThdCreate(PFNTHREAD Routine, PBYTE *pStack, SEL *selStack, TID *tidThread
 /*
     Initialize the thread
 */
     if (rc = DosAllocSeg(sizeof(int) * STACKSIZE, selStack, 0)) return rc;
     *pStack = (PBYTE) MAKEP(*selStack, 0) + STACKSIZE;
     return (rc = DosCreateThread(Routine, tidThread, *pStack));
 }

 void ThdInitialize(HWND hWnd, COM Term) {
 /*
     Initialize Booleans, Master Window
 */
     fBreak = FALSE;
     fAlive = TRUE;
     hWndMaster = hWnd;
 /*
     Initialize TypeAhead buffer
 */
     nBufLoc = -1;
     nChars = 0;
 /*
     Initialize ReadAhead buffer
 */
     cliReadAhead = 0;
 /*
     Spawn off the threads
 */
     rc = ThdCreate(WritePortThread,
                    (PBYTE *) &pStackWPT, &selStackWPT, &tidWPT);
     rc = ThdCreate(ReadPortThread,
                    (PBYTE *) &pStackRPT, &selStackRPT, &tidRPT);
     rc = ThdCreate(WriteScreenThread,
                    (PBYTE *) &pStackWST, &selStackWST, &tidWST);
 /*
     Open up the COM port and the circular queue
 */
     if (rc = ComInit(Term))        { /* Initialize terminal */
         MessageBox(MBE_OPENPORT, NOUPDATE);
     }
     fWrap = Term.fWrap;
 }

 void ThdTerminate(void) {
 /*
     Kill the threads (maybe time delayed) and clean up.
     Yes, I throw away the return codes.

     Problem:  The ReadPort thread might not die, because it blocks
     waiting for a character to be read.
 */
     if (fAlive) {
         fAlive = FALSE;                        /* Kill thread loops
         if (fBreak) rc = ComUnbreak();        /* Remove break signal
         rc = ComClose();                /* Close the port                 */
         DosSemClear(&lSemEmpty);        /* Make WritePort unblock        */
         DosSemClear(&lSemRAFull);        /* Make ReadPort unblock        */
         DosSemClear(&lSemRAEmpty);        /* Make WriteScreen unblock
     }
 }

 void ThdDoBreak(void) {
 /*
     Try to send break for a second
 */
     ComBreak();
     DosSleep(1000L);
     ComUnbreak();
 }

 int ThdPutChar(char ch) {
 /*
     Perhaps we will enter an entire key packet
     But we can't really block, because we gotta respond.
     Solution:  This time, we time out.
 */
 /*
     Manipulate the typeahead buffer (circular queue)
 */
     if (nChars >= BUFSIZE) {                /* Block if buffer full */
         DosSemSet(&lSemFull);
         /*
             Timeout possibility; probably want TIMEOUT < 1 second
         */
         if (rc = DosSemWait(&lSemFull, TIMEOUT)) return rc;
     }
     ThdBufNextLoc(nBufLoc);                /* Increments to next location */
     /*
         Be really impatient...
         This protects the queue, but we should never read/write
         the same queue location.  Maybe I'll move it out later.
     */
     if (rc = DosSemRequest(&lSemLock, TIMEOUT)) { /* Another quick timeout */
         ThdBufLastLoc(nBufLoc);
         return rc;
     }
     TypeAhead[nBufLoc] = ch;
     nChars++;
     DosSemClear(&lSemEmpty);
     DosSemClear(&lSemLock);
     return 0;
 }

 void far WritePortThread(void) {
 /*
     The routine which writes your WM_CHARS to the port,
     one at a time, nice and easy.  We can even wait all
     day (and will, at the rate a user types....)

     The typeahead buffer is protected with semaphores,
     although a move is probably an atomic operation;
     also, it should be secure to read the element, without
     having to protect the buffer.
 */
     int MyLoc = -1;
     char ch;

     while (fAlive) {
         if (nChars < 1) {                /* Wait if the queue is empty */
             DosSemSet(&lSemEmpty);
             DosSemWait(&lSemEmpty, MAXTIMEOUT);
         } else if (!(rc = DosSemRequest(&lSemLock, MAXTIMEOUT))) {
             ThdBufNextLoc(MyLoc);
             ch = TypeAhead[MyLoc];        /* writing to the port is slow, and
             nChars--;                        /* we want to release the semaph
             DosSemClear(&lSemFull);
             DosSemClear(&lSemLock);
             if (rc = ComWrite(TypeAhead[MyLoc])) {
                 /* Post the message... */
                 MessageBox(MBE_WRITEPORT, NOUPDATE);
             }
         }
     }
     DosExit(EXIT_THREAD, 0);
 }

 void far ReadPortThread(void) {
 /*
     Read from the port, a line at a time
     The semaphores force private queue access
 */
     int iLine = 0;

     while (fAlive) {
         if (cliReadAhead < RASIZE) {
             if (ComRead(&aliReadAhead[iLine]))
                 MessageBox(MBE_COMREAD, (MPARAM) ComError());
             else if (&aliReadAhead[iLine].cch) {
                 DosSemRequest(&lSemRALock, MAXTIMEOUT);
                 cliReadAhead++;
                 DosSemClear(&lSemRALock);
                 DosSemClear(&lSemRAEmpty);
                 ThdNextRALoc(iLine);
             }
         } else {
             DosSemSet(&lSemRAFull);
             DosSemWait(&lSemRAFull, TIMEOUT);
         }
     }
     DosExit(EXIT_THREAD, 0);
 }

 int ThdPutString(char a[], int n) {
     int i, rc = 0;

     for (i = 0; i < n; i++)
         if (!rc) rc = ThdPutChar(a[i]);
     return rc;
 }

 void Process(Line lCmd, Line lOutput, int far *pi) {
 /*
     This routine filters characters from the port, before we
     display them to the screen.

     To make this a full blown terminal emulator application,
     all that's needed is to trap cursor movement sequences
     here, and then to retrieve the appropriate queue line.
 */
     USHORT usTemp;
     int i;

     while ((lCmd->szText[*pi] != '\n') && ((*pi) < (int) lCmd->cch)) {
         switch (lCmd->szText[*pi]) {
             case '\b':
                 if (lOutput->cch > 0) lOutput->cch--;
                 break;
             case '\r':
             case '\0':
                 break;
             case '\007':        /* Ctrl G */
                 WinAlarm(HWND_DESKTOP, WA_NOTE);
                 break;
             case '\t':
                 if ((usTemp = (((lOutput->cch >> 3) + 1) << 3)) < MAXLINELEN)
                     for (i = lOutput->cch; i < (SHORT) usTemp; i++)
                         lOutput->szText[i] = ' ';
                     lOutput->cch = usTemp;
                 }
                 break;
             default:
                 if (fWrap) {
                     if (lOutput->cch >= MAXLINELEN) {
                         lOutput->fComplete = TRUE;
                         lOutput->fDrawn = FALSE;
                         return;
                     } else lOutput->szText[lOutput->cch++] = lCmd->szText[*pi
                 } else {
                     if (lOutput->cch < MAXLINELEN) /* Straight nowrap */
                         lOutput->szText[lOutput->cch++] = lCmd->szText[*pi];
                 }
                 break;
         }
         (*pi)++;
     }
     /*
         Complete line if it doesn't exhaust the command string, and
         ends with a newline (\n).  If so, increment string length, but
         don't write (potentially) out of the array bounds.
     */
     if (lOutput->fComplete =
                 (((*pi) < (int) lCmd->cch) && (lCmd->szText[*pi] == '\n')))
         (*pi)++;

     lOutput->fDrawn = FALSE;
 }

 void ThdReset(void) {
     DosSemClear(&lSemOverflow);
 }

 void far WriteScreenThread(void) {
     int                iLine = 0;
     LineInfo        liQueueEntry;
     int                iLinePos = 0;
     BOOL        fMore = FALSE;

     liQueueEntry.cch = 0;
     while (fAlive) {
         if ((cliReadAhead > 0) || fMore) {
             Process(&aliReadAhead[iLine], &liQueueEntry, &iLinePos);
             /*
                 Add the entry to the queue if it's:
                     1) A complete line
                     2) There's nothing more to read
             */
             if (liQueueEntry.fComplete || (cliReadAhead <= 1)) {
                 while (!QueInsertLine(&liQueueEntry)) {
                     MessageBox(MBE_QUEUEFULL, UPDATE);
                     DosSemSet(&lSemOverflow);
                     DosSemWait(&lSemOverflow, MAXTIMEOUT);
                 }
                 /*
                     If complete line is read, reset line pointer
                 */
                 if (liQueueEntry.fComplete) {
                     liQueueEntry.cch = 0;
                     /*
                         If done moving, move cursor to the right place.
                         (the beginning of the next line), so that the
                         user knows when they've hit <Enter>.
                     */
                     if (cliReadAhead <= 1) {
                         liQueueEntry.fComplete = liQueueEntry.fDrawn = FALSE;
                         while (!QueInsertLine(&liQueueEntry)) {
                             MessageBox(MBE_QUEUEFULL, UPDATE);
                             DosSemSet(&lSemOverflow);
                             DosSemWait(&lSemOverflow, MAXTIMEOUT);
                         }
                     }
                 }
             }
             /*
                 If there's no more to process, bump the read pointer
                 and possibly unblock the Read Port thread
             */
             if (!(fMore = (iLinePos < (SHORT)aliReadAhead[iLine].cch))) {
                 ThdNextRALoc(iLine);
                 iLinePos = 0;
                 DosSemRequest(&lSemRALock, MAXTIMEOUT);
                 cliReadAhead--;
                 DosSemClear(&lSemRALock);
                 DosSemClear(&lSemRAFull);
             }
         } else {
             if (fNoUpdate) {
                 fNoUpdate = WinPostMsg(hWndMaster, WM_AVIOUPDATE, NULL, NULL)
             }
             DosSemSet(&lSemRAEmpty);
             DosSemWait(&lSemRAEmpty, TIMEOUT);
         }
     }
     DosExit(EXIT_THREAD, 0);
 }


 TOOL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\TOOL.C

 /*
     TOOL.C -- Contains commonly used routines and globals
     Created by Microsoft Corporation, 1989
 */

 #define NO_DOS
 #define NO_GPI
 #include "tool.h"


 /****************************************************************************
 * This function returns the character following the current one.
 \****************************************************************************

 PSZ  FAR PASCAL NextChar (PSZ lpsz)
     { if (*lpsz) return ++lpsz; else return lpsz; }


 /****************************************************************************
 * This function returns the character previous to the current one.
 \****************************************************************************

 PSZ  FAR PASCAL PrevChar (PSZ lpszStart, PSZ lpszCurrent)
     { if (lpszCurrent > lpszStart) return --lpszCurrent; else return lpszStar


 /****************************************************************************
 * This function transforms a string to upper case.
 \****************************************************************************

 PSZ  FAR PASCAL Upper (PSZ lpsz) {
     PSZ  lpszPtr = lpsz;

     while (*lpszPtr) {
         if (*lpszPtr >= 'a' && *lpszPtr <= 'z') *lpszPtr &= ~0x20;
         lpszPtr++;
         }
     return lpsz;
     }


 TOOL1.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\TOOL1.C

 /*
     TOOL1.C -- More commonly used library routines
     Created by Microsoft Corporation, 1989
 */

 #include "tool.h"

 /***************************************************************************\
 * Merges sz[idMes1] and szText1 and displays a message box using
 * the wStyle. Use caption title provided by szText2.
 * Returns the answer from the message box (MBID_OK, MBID_CANCEL).
 \***************************************************************************/

 int EXPENTRY AlertBox (hwnd, idMes, lpszText1, lpszText2, idHelp, wStyle)
 HWND  hwnd;
 int   idMes;
 PSZ  lpszText1;
 PSZ  lpszText2;
 USHORT idHelp;
 USHORT wStyle;
     {
     char szMessage [MAXMESSAGELENGTH];

     MergeStrings((PSZ)vrgsz[idMes], lpszText1, (PSZ)szMessage);
     if (idHelp != NULL)
         wStyle |= MB_HELP;
     return (WinMessageBox (HWND_DESKTOP, hwnd, (PSZ)szMessage, lpszText2,
                            idHelp, wStyle));
     }


 /***************************************************************************\
 * Scan szSrc for merge spec. If found, insert string szMerge at that point.
 * Then append rest of szSrc NOTE! Merge spec guaranteed to be two chars.
 * returns TRUE if it does a merge, false otherwise.
 \***************************************************************************/

 BOOL EXPENTRY MergeStrings(lpszSrc, lpszMerge, lpszDst)
 PSZ   lpszSrc;
 PSZ   lpszMerge;
 PSZ   lpszDst;
     {
     /* Find merge spec if there is one. */
     while (*(unsigned far *)lpszSrc != *(unsigned *)vrgsz[IDS_MERGE1])
         {
         *lpszDst++ = *lpszSrc;
         /* If we reach end of string before merge spec, just return. */
         if (!*lpszSrc++)
             return FALSE;
         }

     /* If merge spec found, insert sz2 there. (check for null merge string */
     if (lpszMerge)
         {
         while (*lpszMerge)
             *lpszDst++ = *lpszMerge++;
         }

     /* Jump over merge spec */
     lpszSrc++,lpszSrc++;

     /* Now append rest of Src String */
     while (*lpszDst++ = *lpszSrc++);
     return TRUE;
     }



 /****************************************************************************
 * This function invokes either an Open or Save dialog box.
 *     (lpdlf->rgbAction == DLG_OPENDLG)  -->  invoke Open dlgBox
 *     (lpdlf->rgbAction == DLG_SAVEDLG)  -->  invoke Save dlbBox
 *
 * Unless DLG_NOSAVE is specified, the file is opened and left open.
 *
 * Return values are:
 *     TDF_INVALID - Library error (internal error),
 *     TDF_ERRMEM  - Out of memory error
 *     TDF_NOOPEN  - User hits cancel
 *   specific to DLG_OPEN:
 *     TDF_NEWOPEN - Created new file
 *     TDF_OLDOPEN - Opened existing file
 *   specific to DLG_SAVE:
 *     TDF_NEWSAVE - user wants to save to a new file
 *     TDF_OLDSAVE - user wants to save over existing file
 *   specific to DLG_NOSAVE:
 *     TDF_NEWSAVE - user wants to save to a new file
 *     TDF_OLDSAVE - user wants to save over existing file
 \****************************************************************************

 int EXPENTRY DlgFile(hwnd, pdlf)
 HWND  hwnd;
 PDLF pdlf;
     {
     /* create dialog box */
     if (pdlf->rgbAction & DLG_SAVEDLG)
         return WinDlgBox(HWND_DESKTOP, hwnd, DlgSaveAsWndProc, vhModule,
                          IDD_SAVEAS, (PSZ)pdlf);
     else
         return WinDlgBox(HWND_DESKTOP, hwnd, DlgOpenWndProc, vhModule,
                          IDD_OPEN, (PSZ)pdlf);
     }

 /**************************************************************************\
 * This function initializes the DLF structure
 \**************************************************************************/
 void EXPENTRY SetupDLF(PDLF pdlf, int iAction, PHFILE phFile,
                        PSZ Extension, PSZ AppName, PSZ pszInTitle,
                        PSZ pszInInstructions ) {
     pdlf->pszExt = Extension;
     pdlf->rgbFlags = ATTRDIRLIST;
     pdlf->phFile = phFile;
     pdlf->pszAppName = AppName;
     pdlf->rgbAction = iAction;
     pdlf->pszTitle = pszInTitle;
     pdlf->pszInstructions = pszInInstructions;
 }


 TOOLA.ASM
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\OPENDLG\TOOLA.ASM

 ;/*
 ; *   File Dialog Library
 ; *   Created by Microsoft Corporation, 1989
 ; */
 .xlist
 include cmacros.inc
 .list

 errnz   ?PLM-1          ; this module must use Pascal convention

 TRUE   = 1

 externFP        <WinCreateHeap, WinDestroyHeap>

 sBegin DATA
 externW vhModule
 externD vhheap
 globalW __acrtused,0
 sEnd    DATA

 sBegin CODE

 assumes cs,CODE
 assumes ds,NOTHING

 ; The following trick is to avoid any need for a DS where a constant
 ;   string is needed, any (PSZ)"foo" is replaced by (PSZ)szfoo with
 ;   szfoo created in the Code Selector. As we can't do that with C,
 ;   we do it with MASM.
 ;
         PUBLIC        szStarStar
 szStarStar  LABEL   BYTE
         DB        '*','.','*',0
         PUBLIC  szDot
 szDot   LABEL   BYTE
         DB      '.',0

 ;======================================================
 ; LCopyStruct(pbSrc, pbDst, cb)
 ;
 cProc   LCopyStruct, <PUBLIC, NODATA, FAR>, <ds,si,di>
 ParmD lpchSrc
 ParmD lpchDst
 ParmW cb

 cBegin
         mov     cx, cb
         jcxz    lcopydone           ; all done if crc   == 0
         lds     si, lpchSrc
         les     di, lpchDst
         cmp     si,di
         jae     lcopyok
         mov     ax,cx
         dec     ax
         add     si,ax
         add     di,ax
         std
         rep     movsb
         cld
         jmp     short lcopydone
 lcopyok:
         cld
         rep     movsb
 lcopydone:

 cEnd


 ;======================================================
 ; LFillStruct(pbSrc, pbDst, cb)
 ;
 cProc   LFillStruct, <PUBLIC, NODATA, FAR>, <di>
 ParmD lpchDst
 ParmW cb
 ParmB fillByte

 cBegin
         mov     cx, cb
         jcxz    lfilldone           ; all done if crc   == 0
         les     di, lpchDst
         cld
         mov     al,fillByte
         rep     stosb
 lfilldone:
 cEnd
 ;======================================================
 ;
 ; lstrlen: Same as strlen except for taking long ptrs
 ;

 cProc   lstrlen,<PUBLIC, NODATA, FAR>
 ;       parmD   pStr
 cBegin  nogen
         mov     bx,sp
         push    di
         les     di,ss:[bx+4]
         cld
         xor     ax,ax                   ; get zero in ax
         mov     cx,-1                   ; at most 64 k to move
         repnz   scasb                   ; look for end
         mov     ax,cx
         neg     ax
         dec     ax
         dec     ax
         pop     di
         ret     4
 cEnd    nogen


 lstrsetup:
         pop     dx
         mov     bx,sp

         push    ds
         push    si
         push    di

         lds     si,ss:[bx+4]
         les     di,ss:[bx+8]
         regptr  dssi,ds,si
         regptr  esdi,es,di
         cld
         jmp     dx

 lstrfinish  proc    far
         pop     di
         pop     si
         pop     ds
         ret     8
 lstrfinish  endp


 ;======================================================
 ;
 ;lstrcpy: strcpy with long pointers
 ;
 cProc   lstrcpy,<PUBLIC, NODATA, FAR>
 ;       parmD   pDst                    ; [bx+8]
 ;       parmD   pSrc                    ; [bx+4]

 cBegin  nogen
         call    lstrsetup
 lcp1:   lodsb
         stosb
         or      al,al
         jnz     lcp1
         mov     ax,di                   ; point at last byte copied
         dec     ax
         mov     dx,es                   ; and its segment
         jmp     lstrfinish
 cEnd    nogen


 ;======================================================
 ;
 ;lstrcat: Same as strcat except with long ptrs.
 ;
 cProc   lstrcat,<PUBLIC, NODATA, FAR>
 ;   parmD   pDst
 ;   parmD   pSrc

 cBegin  nogen
         call    lstrsetup
         xor     ax,ax                   ; get zero in ax
         mov     cx,-1                   ; at most 64 k to look
         repnz   scasb                   ; look for end
         dec     di                      ; Point at null byte
         jmp     lcp1                    ; jump to lstrcpy loop
 cEnd    nogen

         ; convert lower case to upper, must preserve es,di,cx
         public  MyUpper
 MyUpper:
         cmp     al,'a'
         jb      myu2
         cmp     al,'z'
         jbe     myu1
         ja      myu2
 myu1:   sub     al,'a'-'A'
 myu2:   ret

         ; convert upper case to lower, must preserve es,di,cx
         public  MyLower
 MyLower:
         cmp     al,'A'
         jb      myl2
         cmp     al,'Z'
         jbe     myl1

         ja      myl2
 myl1:   add     al,'a'-'A'
 myl2:   ret

 ;=========================================
 ;
 ;lstrcmp:   long ptr lVersion of strcmp
 ;
 cProc   lstrcmp,<PUBLIC, NODATA, FAR>
 ;       parmD   ps1
 ;       parmD   ps2
 cBegin  nogen
         call    lstrsetup
 lcmploop:
         xor     ax,ax                   ; AH needs to be zero inside this loo
         cmp     byte ptr [si],al
         je      lsidone                 ; si is finished check for di finish
         cmp     es:byte ptr [di],al
         je      ldismall                ; di finished before si
         lodsb
         mov     bx,ax
         mov     al,es:[di]
         inc     di
         cmp     ax,bx
         je      lcmploop                ; still equal
         mov     al,0                    ; preverve flags
         jb      lsismall                ; si is less than di
 ldismall:
         inc     ax
         jmp     lstrfinish
 lsidone:
         cmp     byte ptr es:[di],0
         je      lstrfinish
 lsismall:
         dec     ax
         jmp     lstrfinish
 cEnd    nogen
 sEnd CODE


 createSeg _INIT,INIT,byte,public,CODE

 sBegin  INIT
 externNP    <InitLibrary>
 assumes CS,INIT
 assumes DS,DATA

 ;======================================================
 ; BOOL FAR PASCAL LibInit ()
 ;
 ; Initializes the library
 ; int dataSelector       /* DS = automatic data selector */
 ; int wHeapSize;         /* SI = heap size */
 ; int hmod;           /* DI = module handle */
 ;     {
 ;     vhModule = hmod;
 ;     if (!(vhheap = WinCreateHeap(dataSelector, wHeapsize, 0, 0, 0, 0)))
 ;         return FALSE;;
 ;     return (InitLibrary());
 ;     }
 ;
 ; DS already usSet
 ;
 cProc   LibInit,<FAR, PUBLIC>
 cBegin  nogen
         mov     [vhModule],di           ; Remember module handle

         sub     ax,ax
         cCall   WinCreateHeap,<ds, si, ax, ax, ax, ax>
         mov     bx, ax
         or      ax, dx
         jz      loaddone                ; Heap wasn't created
         mov     word ptr vhheap, bx
         mov     word ptr vhheap + 2, dx

         cCall   InitLibrary                ; continue initialization
         or      ax,ax
         jnz     loaddone                ; OK, init done
         cCall   WinDestroyHeap,<vhheap> ; Non OK, destroy the heap
 ;
 ; Return non-zero to indicate successful initialization
 ;
 loaddone:
         ret
 cEnd

 sEnd    INIT
         END LibInit


 UTIL.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\DDEML\MSNGR\UTIL.C

 /****************************** Module Header ******************************\
 * Module Name:  util.c
 *
 * Utility functions used by msngr app.
 *
 * Created:  1/1/89  sanfords
 *
 * Copyright (c) 1988, 1989  Microsoft Corporation
 \***************************************************************************/
 #include "msngr.h"

 extern NPUSERLIST gnpUL;
 extern HAB hab;

 /***************************** Public  Function ****************************\
 * Concatonates psz1 and psz2 into pszDest.
 *
 * History:  1/1/89  created sanfords
 \***************************************************************************/
 void lstrcat(pszDest, psz1, psz2)
 PSZ pszDest, psz1, psz2;
 {
     while (*psz1 != '\0') {
         *pszDest++ = *psz1++;
     }
     while (*psz2 != '\0') {
         *pszDest++ = *psz2++;
     }
     *pszDest = '\0';
 }


 void lstrcpy(pszDst, pszSrc)
 PSZ pszDst, pszSrc;
 {
     lstrcat(pszDst, pszSrc, "");
 }


 /***************************** Public  Function ****************************\
 * Concatonates psz1 and psz2 into pszDest but leaves a '\0' in between.
 *
 * History:  1/1/89  created sanfords
 \***************************************************************************/
 void lstrpak(pszDest, psz1, psz2)
 PSZ pszDest, psz1, psz2;
 {
     while (*psz1 != '\0') {
         *pszDest++ = *psz1++;
     }
     *pszDest++ = '\0';
     while (*psz2 != '\0') {
         *pszDest++ = *psz2++;
     }
     *pszDest = '\0';
 }
 /***************************** Private Function ****************************\
 *
 * returns string length not counting null terminator.
 *
 * History:  1/1/89  created     sanfords
 \***************************************************************************/
 int lstrlen(psz)
 PSZ psz;
 {
     int c = 0;

     while (*psz != 0) {
         psz++;
         c++;
     }
     return(c);
 }



 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   These functions handle hConv-hsz relationship control.
 *
 * History:      8/23/89 Created         sanfords
 \***************************************************************************/
 void DestroyUser(npUL)
 NPUSERLIST npUL;
 {
     PNPUSERLIST ppList;

     ppList = &gnpUL;
     while (*ppList && *ppList != npUL) {
         ppList = &((*ppList)->next);
     }
     if (!ppList) {
         return;
     }
     DdeFreeHsz((*ppList)->hsz);
     if (WinIsWindow(hab, (*ppList)->hwndLink))
         WinDestroyWindow((*ppList)->hwndLink);
     if ((*ppList)->hConvMsg) {
         DdeDisconnect((*ppList)->hConvMsg);
     }
     freeUserList(ppList);
 }


 /***************************** Private Function ****************************\
 * DESCRIPTION:
 *   These functions handle hConv-hsz relationship control.
 *
 * History:      1/13/89 Created         sanfords
 \***************************************************************************/
 VOID AddUser(
 HCONV hConvMsg,
 HSZ hsz,
 HAPP hApp)
 {
     NPUSERLIST pList;
     UCHAR sz[MAX_NAMESTR + 1];
     SHORT lit;
     extern HWND hwndLB;

     if ((pList = (NPUSERLIST)WinAllocMem(hheap, sizeof(USERLIST))) == 0)
         return;
     pList->hConvMsg = hConvMsg;
     pList->hConvLink = 0;
     pList->hwndLink = NULL;
     pList->hsz = hsz;
     pList->hApp = hApp;
     pList->next = gnpUL;  /* link in front */
     gnpUL = pList;
     DdeGetHszString(hsz, sz, MAX_NAMESTR + 1L);
     lit = (SHORT)WinSendMsg(hwndLB, LM_INSERTITEM,
             MPFROMSHORT(LIT_SORTASCENDING), MPFROMP(sz));
     WinSendMsg(hwndLB, LM_SETITEMHANDLE, (MPARAM)lit, (MPARAM)pList);
 }





 NPUSERLIST FindUser(pList, hsz)
 NPUSERLIST pList;
 HSZ hsz;
 {
     while (pList) {
         if (hsz == pList->hsz)
             return(pList);
         pList = pList->next;
     }
     return(0);

 }




 void freeUserList(ppList)
 PNPUSERLIST ppList;
 {
     NPUSERLIST pListT;

     while (*ppList) {
         pListT = *ppList;
         *ppList = pListT->next;
         WinFreeMem(hheap, (NPBYTE)pListT, sizeof(USERLIST));
     }
 }




 PVOID FarAllocMem(hheap, cb)
 HHEAP hheap;
 USHORT cb;
 {
     return((PVOID)((PBYTE)WinLockHeap(hheap) + (USHORT)WinAllocMem(hheap, cb)
 }



 void MyPostError(err)
 USHORT err;
 {
     switch (err) {
     case DMGERR_SERVER_DIED:
     case DMGERR_NO_CONV_ESTABLISHED:
         break;
     default:
         DdePostError(err);
         break;
     }
 }






 UTILS.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\INIEDIT\UTILS.C

 /******************************* Module Header ******************************
 * Module Name: Utils.c
 *
 *
 *
 *
 * PM OS2.ini Editor
 *
 * Miscellaneous Utility Functions
 *
 *
 \***************************************************************************/

 #define LINT_ARGS
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>

 #define INCL_WINERRORS
 #define INCL_WINMESSAGEMGR

 #include <os2.h>

 #include "IniEdit.h"

 /******************************** Globals **********************************/


 /******************************* Externals *********************************/

 extern HAB habIniEdit;

 void ErrMessage( char *szMsg )
 {

     char      szMsgBuf[132];
     ERRORID   ErrorNo;

     ErrorNo = WinGetLastError( habIniEdit );

     sprintf( szMsgBuf, "%s; Error: %p", szMsg, ErrorNo );

     WinMessageBox( HWND_DESKTOP, HWND_DESKTOP, szMsgBuf, "System Print", 0,
          MB_ICONEXCLAMATION );
 }


 VECTFONT.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VECTFONT.C

 /*----------------------------------------
    VECTFONT.C -- Vector Font Demo Program
   ----------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "VectFont" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc,
                             CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - Vector Font Demo", 0L,
                                      (HMODULE) NULL, ID_RESOURCE, &hwndClient

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1,
                                                        MPARAM mp2)
      {
      static struct {
                    SHORT idCmd ;
                    VOID (*fn) (HPS, LONG, LONG) ;
                    }
                    vectfont [] = {
                                  IDM_NOTHING,     NULL,
                                  IDM_24POINT,     Display_24Point,
                                  IDM_MIRROR,      Display_Mirror,
                                  IDM_STRETCH,     Display_Stretch,
                                  IDM_CHARANGLE,   Display_CharAngle,
                                  IDM_ROTATE,      Display_Rotate,
                                  IDM_CHARSHEAR,   Display_CharShear,
                                  IDM_SHADOW,      Display_Shadow,
                                  IDM_HOLLOW,      Display_Hollow,
                                  IDM_DROPSHADOW,  Display_DropShadow,
                                  IDM_BLOCK,       Display_Block,
                                  IDM_NEON,        Display_Neon,
                                  IDM_FADE,        Display_Fade,
                                  IDM_SPOKES,      Display_Spokes,
                                  IDM_WAVY,        Display_Wavy,
                                  IDM_MODSPOKES,   Display_ModSpokes
                                  } ;
      static HDC    hdc ;
      static HPS    hps ;
      static HWND   hwndMenu ;
      static POINTL ptlClient ;
      static SHORT  sNumRoutines = sizeof vectfont / sizeof vectfont[0],
                    sDisplay = IDM_NOTHING ;
      INT           i ;
      RECTL         rcl ;
      SIZEL         sizl ;

      switch (msg)
           {
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                                    // Create PS use Twips page units
                sizl.cx = 0 ;
                sizl.cy = 0 ;
                hps = GpiCreatePS (hab, hdc, &sizl,
                                        PU_TWIPS     | GPIF_DEFAULT |
                                        GPIT_MICRO   | GPIA_ASSOC) ;

                                    // Adjust Page Viewport for points

                GpiQueryPageViewport (hps, &rcl) ;
                rcl.xRight *= 20 ;
                rcl.yTop   *= 20 ;
                GpiSetPageViewport (hps, &rcl) ;

                hwndMenu = WinWindowFromID (
                                WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                FID_MENU) ;
                return 0 ;

           case WM_SIZE:
                ptlClient.x = SHORT1FROMMP (mp2) ;      // client width
                ptlClient.y = SHORT2FROMMP (mp2) ;      // client height

                GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptlClient);
                return 0 ;

           case WM_COMMAND:
                for (i = 0 ; i < sNumRoutines ; i++)
                     if (COMMANDMSG(&msg)->cmd == (USHORT) vectfont[i].idCmd)
                          {
                          if ((USHORT) sDisplay == COMMANDMSG(&msg)->cmd)
                               return 0 ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sDisplay, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, 0)) ;

                          sDisplay = COMMANDMSG(&msg)->cmd ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sDisplay, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED,
                                                    MIA_CHECKED)) ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;
                          }
                break ;

           case WM_PAINT:
                WinBeginPaint (hwnd, hps, NULL) ;
                GpiErase (hps) ;
                                    // Display hourglass pointer

                WinSetPointer (HWND_DESKTOP,
                     WinQuerySysPointer (HWND_DESKTOP,SPTR_WAIT,FALSE));

                if (!WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT))
                     WinShowPointer (HWND_DESKTOP, TRUE) ;

                                    // Execute font routine

                for (i = 0 ; i < sNumRoutines ; i++)
                     if (sDisplay == vectfont[i].idCmd)
                          {
                          if (vectfont[i].fn != NULL)
                               {
                               GpiSavePS (hps) ;
                               vectfont[i].fn (hps, ptlClient.x,
                                                    ptlClient.y) ;
                               GpiRestorePS (hps, -1L) ;
                               }
                          break ;
                          }
                                         // Display arrow pointer

                if (!WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT))
                     WinShowPointer (HWND_DESKTOP, FALSE) ;

                WinSetPointer (HWND_DESKTOP,
                     WinQuerySysPointer (HWND_DESKTOP,SPTR_ARROW,FALSE));

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                GpiDestroyPS (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 VF00.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF00.C

 /*--------------------------------------------------
    VF00.C -- Routines for working with vector fonts
   --------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "vectfont.h"

 extern HAB hab ;

 LONG CreateVectorFont (HPS hps, LONG lcid, CHAR *szFacename)
      {
      FATTRS fat ;

      fat.usRecordLength  = sizeof fat ;
      fat.fsSelection     = 0 ;
      fat.lMatch          = 0 ;
      fat.idRegistry      = 0 ;
      fat.usCodePage      = GpiQueryCp (hps) ;
      fat.lMaxBaselineExt = 0 ;
      fat.lAveCharWidth   = 0 ;
      fat.fsType          = 0 ;
      fat.fsFontUse       = FATTR_FONTUSE_OUTLINE |
                            FATTR_FONTUSE_TRANSFORMABLE ;

      strcpy (fat.szFacename, szFacename) ;

      return GpiCreateLogFont (hps, NULL, lcid, &fat) ;
      }

 BOOL ScaleVectorFont (HPS hps, SHORT xPointSize, SHORT yPointSize)
      {
      HDC    hdc ;
      LONG   xDeviceRes, yDeviceRes ;
      POINTL ptlFont ;
      SIZEF  sizfx ;
                          // Get device resolution in pixels per meter

      hdc = GpiQueryDevice (hps) ;

      DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &xDeviceRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,   1L, &yDeviceRes) ;

                          // Find desired font size in pixels

      ptlFont.x = 254L * xPointSize * xDeviceRes / 7200000L ;
      ptlFont.y = 254L * yPointSize * yDeviceRes / 7200000L ;

                          // Convert to page units

      GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptlFont) ;

                          // Set the character box

      sizfx.cx = MAKEFIXED (ptlFont.x, 0) ;
      sizfx.cy = MAKEFIXED (ptlFont.y, 0) ;

      return GpiSetCharBox (hps, &sizfx) ;
      }

 BOOL ScaleFontToBox (HPS hps, LONG cbText, CHAR *szText, LONG cxBox,
                                                          LONG cyBox)
      {
      POINTL aptl[TXTBOX_COUNT] ;
      SIZEF  sizfx ;

      GpiQueryCharBox (hps, &sizfx) ;
      GpiQueryTextBox (hps, cbText, szText, TXTBOX_COUNT, aptl) ;

      sizfx.cx = sizfx.cx /
           (max (aptl[TXTBOX_TOPRIGHT].x, aptl[TXTBOX_BOTTOMRIGHT].x) -
            min (aptl[TXTBOX_TOPLEFT].x,  aptl[TXTBOX_BOTTOMLEFT].x))
           * cxBox ;

      sizfx.cy = sizfx.cy /
           (max (aptl[TXTBOX_TOPRIGHT].y,    aptl[TXTBOX_TOPLEFT].y) -
            min (aptl[TXTBOX_BOTTOMRIGHT].y, aptl[TXTBOX_BOTTOMLEFT].y))
           * cyBox ;

      return GpiSetCharBox (hps, &sizfx) ;
      }

 VOID QueryStartPointInTextBox (HPS hps, LONG cbText, CHAR *szText,
                                         POINTL *pptl)
      {
      POINTL aptl[TXTBOX_COUNT] ;

      GpiQueryTextBox (hps, cbText, szText, TXTBOX_COUNT, aptl) ;

      pptl->x = max(-aptl[TXTBOX_TOPLEFT].x, -aptl[TXTBOX_BOTTOMLEFT].x);
      pptl->y = max(-aptl[TXTBOX_TOPLEFT].y, -aptl[TXTBOX_BOTTOMLEFT].y);
      }

 VOID ColorClient (HPS hps, LONG cxClient, LONG cyClient, LONG lColor)
      {
      RECTL rcl ;

      WinSetRect (hab, &rcl, 0, 0, (SHORT) cxClient, (SHORT) cyClient) ;
      WinFillRect (hps, &rcl, lColor) ;
      }


 VF01.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF01.C

 /*-----------------------------------------
    VF01.C -- Display 24-point vector fonts
   -----------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <string.h>
 #include "vectfont.h"

 VOID Display_24Point (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR *szFacename[] = {
                                  "Courier",      "Courier Italic",
                                  "Courier Bold", "Courier Bold Italic",
                                  "Tms Rmn",      "Tms Rmn Italic",
                                  "Tms Rmn Bold", "Tms Rmn Bold Italic",
                                  "Helv",         "Helv Italic",
                                  "Helv Bold",    "Helv Bold Italic"
                                  } ;
      static INT  iNumFonts = sizeof szFacename / sizeof szFacename[0] ;
      FONTMETRICS fm ;
      INT         iFont ;
      POINTL      ptl ;

      ptl.x = cxClient / 8 ;
      ptl.y = cyClient ;

      for (iFont = 0 ; iFont < iNumFonts ; iFont++)
           {
                                    // Create font, select it and scale

           CreateVectorFont (hps, LCID_MYFONT, szFacename[iFont]) ;
           GpiSetCharSet (hps, LCID_MYFONT) ;
           ScaleVectorFont (hps, 240, 240) ;

                                    // Get font metrics for scaled font

           GpiQueryFontMetrics (hps, (LONG) sizeof (FONTMETRICS), &fm) ;
           ptl.y -= fm.lMaxBaselineExt ;

                                    // Display the font facename

           GpiCharStringAt (hps, &ptl, (LONG) strlen (szFacename[iFont]),
                            szFacename[iFont]) ;

           GpiCharString (hps, 10L, " - abcdefg") ;

           GpiSetCharSet (hps, LCID_DEFAULT) ;     // Clean up
           GpiDeleteSetId (hps, LCID_MYFONT) ;
           }
      }


 VF02.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF02.C

 /*----------------------------------------------------------
    VF02.C -- Display vector font stretched to client window
   ----------------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Stretch (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Hello!" ;
      static LONG cbText = sizeof szText - 1 ;
      POINTL      ptl ;

                               // Create font, select, and scale

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Display text

      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF03.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF03.C

 /*------------------------------------------------------
    VF03.C -- Display four strings in mirror reflections
   ------------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Mirror (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Mirror" ;
      static LONG cbText = sizeof szText - 1 ;
      INT         i ;
      POINTL      ptl ;
      SIZEF       sizfx ;
                                    // Create font, select and scale

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient / 2, cyClient / 2) ;

      ptl.x = cxClient / 2 ;        // Center of client window
      ptl.y = cyClient / 2 ;

      for (i = 0 ; i < 4 ; i++)
           {
           GpiQueryCharBox (hps, &sizfx) ;

           if (i == 1 || i == 3)
                sizfx.cx *= -1 ;
                                    // Negate char box dimensions
           if (i == 2)
                sizfx.cy *= -1 ;

           GpiSetCharBox (hps, &sizfx) ;

           GpiCharStringAt (hps, &ptl, cbText, szText) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;          // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;

      }


 VF04.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF04.C

 /*------------------------------------------
    VF04.C -- Display eight character angles
   ------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include "vectfont.h"

 VOID Display_CharAngle (HPS hps, LONG cxClient, LONG cyClient)
      {
      static GRADIENTL agradl[8] = { 100,    0,  100,  100,
                                       0,  100, -100,  100,
                                    -100,    0, -100, -100,
                                       0, -100,  100, -100 } ;
      CHAR             szBuffer[40] ;
      INT              iIndex ;
      POINTL           ptl ;

                               // Create Helvetica font

      CreateVectorFont (hps, LCID_MYFONT, "Helv") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleVectorFont (hps, 200, 200) ;

      ptl.x = cxClient / 2 ;   // Center of client window
      ptl.y = cyClient / 2 ;

      for (iIndex = 0 ; iIndex < 8 ; iIndex++)
           {
           GpiSetCharAngle (hps, agradl + iIndex) ;     // Char angle

           GpiCharStringAt (hps, &ptl,
                (LONG) sprintf (szBuffer, " Character Angle (%ld,%ld)",
                                agradl[iIndex].x, agradl[iIndex].y),
                szBuffer) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF05.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF05.C

 /*--------------------------------------------
    VF05.C -- Display "Hello, world" in circle
   --------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <math.h>
 #include <stdlib.h>
 #include "vectfont.h"

 VOID Display_Rotate (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Hello, world! " ;
      static LONG cbText = sizeof szText - 1L ;
      static LONG alWidthTable[256] ;
      double      ang, angCharWidth, angChar ;
      FONTMETRICS fm ;
      GRADIENTL   gradl ;
      INT         iChar ;
      LONG        lCircum, lRadius, lTotWidth, lCharRadius, cyChar ;
      POINTL      ptl ;

                          // Create the font and get font metrics

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;

      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                          // Find circle dimensions and scale font

      lRadius = min (cxClient / 4, cyClient / 4) ;
      lCircum = (LONG) (2 * PI * lRadius) ;
      cyChar  = fm.lMaxBaselineExt * lRadius / fm.lMaxAscender ;

      ScaleFontToBox (hps, cbText, szText, lCircum, cyChar) ;

                          // Obtain width table and total width

      GpiQueryWidthTable (hps, 0L, 256L, alWidthTable) ;

      for (lTotWidth = 0, iChar = 0 ; iChar < (INT) cbText ; iChar ++)
           lTotWidth += alWidthTable [szText [iChar]] ;

      ang = PI / 2 ;      // Initial angle for first character

      for (iChar = 0 ; iChar < (INT) cbText ; iChar++)
           {
                               // Set character angle

           angCharWidth = 2 * PI * alWidthTable [szText [iChar]] / lTotWidth ;

           gradl.x = (LONG) (lRadius * cos (ang - angCharWidth / 2 - PI / 2))
           gradl.y = (LONG) (lRadius * sin (ang - angCharWidth / 2 - PI / 2))

           GpiSetCharAngle (hps, &gradl) ;

                               // Find position for character and display it

           angChar = atan2 ((double) alWidthTable [szText [iChar]] / 2,
                            (double) lRadius) ;

           lCharRadius = (LONG) (lRadius / cos (angChar)) ;
           angChar += ang - angCharWidth / 2 ;

           ptl.x = (LONG) (cxClient / 2 + lCharRadius * cos (angChar)) ;
           ptl.y = (LONG) (cyClient / 2 + lCharRadius * sin (angChar)) ;

           GpiCharStringAt (hps, &ptl, 1L, szText + iChar) ;

           ang -= angCharWidth ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;          // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF06.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF06.C

 /*----------------------------------------------------------
    VF06.C -- Display seven different character shear angles
   ----------------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include "vectfont.h"

 VOID Display_CharShear (HPS hps, LONG cxClient, LONG cyClient)
      {
      static POINTL aptlShear[7] = { -100,  41, -100, 100,
                                      -41, 100,    0, 100,
                                       41, 100,  100, 100,
                                      100,  41 } ;
      CHAR          szBuffer[40] ;
      FONTMETRICS   fm ;
      INT           iIndex ;
      POINTL        ptl ;

                          // Create and scale Helvetica font

      CreateVectorFont (hps, LCID_MYFONT, "Helv") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleVectorFont (hps, 480, 480) ;

                          // Get font metrics for scaled font

      GpiQueryFontMetrics (hps, (LONG) sizeof (FONTMETRICS), &fm) ;

      ptl.x = cxClient / 8 ;
      ptl.y = cyClient ;

      for (iIndex = 0 ; iIndex < 7 ; iIndex++)
           {
           GpiSetCharShear (hps, aptlShear + iIndex) ;  // Char shear

           ptl.y -= fm.lMaxBaselineExt ;

           GpiCharStringAt (hps, &ptl,
                (LONG) sprintf (szBuffer, "Character Shear (%ld,%ld)",
                          aptlShear[iIndex].x, aptlShear[iIndex].y),
                szBuffer) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF07.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF07.C

 /*--------------------------------------------------
    VF07.C -- Display characters with sheared shadow
   --------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Shadow (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Shadow" ;
      static LONG cbText = sizeof szText - 1 ;
      POINTL      ptl, ptlShear ;
      SIZEF       sizfx ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, 3 * cxClient / 4, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_BLUE) ;

      GpiSavePS (hps) ;

      ptlShear.x = 200 ;                                // Set char shear
      ptlShear.y = 100 ;
      GpiSetCharShear (hps, &ptlShear) ;

      GpiQueryCharBox (hps, &sizfx) ;
      sizfx.cy += sizfx.cy / 4 ;                        // Set char box
      GpiSetCharBox (hps, &sizfx) ;

      GpiSetColor (hps, CLR_DARKBLUE) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Display shadow

      GpiRestorePS (hps, -1L) ;

      GpiSetColor (hps, CLR_RED) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Display text

      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF08.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF08.C

 /*-----------------------
    VF08.C -- Hollow font
   -----------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Hollow (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Hollow" ;
      static LONG cbText = sizeof szText - 1 ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text in path
      GpiEndPath (hps) ;

      GpiStrokePath (hps, ID_PATH, 0L) ;                // Stroke path

      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF09.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF09.C

 /*---------------------------------
    VF09.C -- Font with Drop Shadow
   ---------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_DropShadow (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Hello!" ;
      static LONG cbText = sizeof szText - 1 ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Shadow

      ptl.x -= 12 ;       // 1/6 inch
      ptl.y += 12 ;

      GpiSetColor (hps, CLR_BACKGROUND) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Outline
      GpiEndPath (hps) ;

      GpiSetColor (hps, CLR_NEUTRAL) ;
      GpiStrokePath (hps, ID_PATH, 0L) ;

      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF10.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF10.C

 /*----------------------------
    VF10.C -- Solid block font
   ----------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Block (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = " Block " ;
      static LONG cbText = sizeof szText - 1 ;
      INT         i ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_WHITE) ;
      GpiSetColor (hps, CLR_DARKGREEN) ;

      for (i = 0 ; i < 18 ; i++)
           {
           GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Block

           ptl.x -= 1 ;
           ptl.y -= 1 ;
           }

      GpiSetColor (hps, CLR_GREEN) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Outline
      GpiEndPath (hps) ;

      GpiSetColor (hps, CLR_DARKGREEN) ;
      GpiStrokePath (hps, ID_PATH, 0L) ;

      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF11.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF11.C

 /*-----------------------------------------------------
    VF11.C -- Neon font using geometrically-thick lines
   -----------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Neon (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = " Neon " ;
      static LONG cbText = sizeof szText - 1 ;
      static LONG lForeColor[] = { CLR_DARKRED, CLR_DARKRED, CLR_RED,
                                   CLR_RED,     CLR_WHITE,   CLR_WHITE };
      static LONG lBackColor[] = { CLR_BLACK,   CLR_DARKRED, CLR_DARKRED,
                                   CLR_RED,     CLR_RED,     CLR_WHITE };
      static LONG lWidth[] = { 34, 28, 22, 16, 10, 4 } ;

      INT         iIndex ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;

      for (iIndex = 0 ; iIndex < 6 ; iIndex++)
           {
           GpiBeginPath (hps, ID_PATH) ;
           GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text out
           GpiEndPath (hps) ;

           GpiSetColor (hps, lForeColor[iIndex]) ;
           GpiSetBackColor (hps, lBackColor[iIndex]) ;
           GpiSetBackMix (hps, BM_OVERPAINT) ;
           GpiSetPattern (hps, PATSYM_HALFTONE) ;
           GpiSetLineWidthGeom (hps, lWidth[iIndex]) ;

           GpiStrokePath (hps, ID_PATH, 0L) ;           // Stroke path
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF12.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF12.C

 /*------------------------------------------------------
    VF12.C -- Fading font with various pattern densities
   ------------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include "vectfont.h"

 VOID Display_Fade (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Fade" ;
      static LONG cbText = sizeof szText - 1 ;
      LONG        lPattern ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      GpiSetBackMix (hps, BM_OVERPAINT) ;

      for (lPattern = 8 ; lPattern >= 1 ; lPattern--)
           {
           GpiBeginPath (hps, ID_PATH) ;
           GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text out
           GpiEndPath (hps) ;

           GpiSetPattern (hps, lPattern) ;
           GpiFillPath (hps, ID_PATH, FPATH_ALTERNATE) ;     // Fill path

           ptl.x += 2 ;
           ptl.y -= 2 ;
           }

      GpiSetPattern (hps, PATSYM_SOLID) ;
      GpiSetBackMix (hps, BM_LEAVEALONE) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;          // Solid

      GpiSetCharSet (hps, LCID_DEFAULT) ;                    // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF13.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF13.C

 /*--------------------------
    VF13.C -- Clipped Spokes
   --------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <math.h>
 #include "vectfont.h"

 VOID Display_Spokes (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "WOW" ;
      static LONG cbText = sizeof szText - 1 ;
      static LONG lColors[] = { CLR_BLUE, CLR_GREEN, CLR_CYAN,
                                CLR_RED,  CLR_PINK,  CLR_YELLOW,
                                CLR_WHITE } ;
      double      dMaxRadius ;
      INT         i, iNumColors = sizeof lColors / sizeof lColors[0] ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
      GpiEndPath (hps) ;

      GpiSetClipPath (hps, ID_PATH, SCP_AND | SCP_ALTERNATE) ;

      dMaxRadius = sqrt (pow (cxClient / 2.0, 2.0) +
                         pow (cyClient / 2.0, 2.0)) ;
                                                        // Draw spokes
      for (i = 0 ; i < 360 ; i++)
           {
           GpiSetColor (hps, lColors[i % iNumColors]) ;

           ptl.x = cxClient / 2 ;
           ptl.y = cyClient / 2 ;
           GpiMove (hps, &ptl) ;

           ptl.x += (LONG) (dMaxRadius * cos (i * 6.28 / 360)) ;
           ptl.y += (LONG) (dMaxRadius * sin (i * 6.28 / 360)) ;
           GpiLine (hps, &ptl) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF14.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF14.C

 /*--------------------------------------
    VF14.C -- Clipped wavy spline curves
   --------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include "vectfont.h"

 VOID Display_Wavy (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "Hello!" ;
      static LONG cbText = sizeof szText - 1 ;
      static LONG lColors[] = { CLR_BLUE, CLR_GREEN,  CLR_CYAN, CLR_RED,
                                CLR_PINK, CLR_YELLOW, CLR_WHITE } ;
      INT         i ;
      POINTL      ptl, aptl[8] ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
      GpiEndPath (hps) ;

      GpiSetClipPath (hps, ID_PATH, SCP_AND | SCP_ALTERNATE) ;

      for (i = 0 ; i < 14 ; i++)
           {
           aptl[0].x = 0 ;
           aptl[0].y = i * cyClient / 14 ;

           aptl[1].x = cxClient / 3 ;
           aptl[1].y = min (cyClient, 2 * i * cyClient / 14) ;

           aptl[2].x = 2 * cxClient / 3 ;
           aptl[2].y = max (0L, (2 * i - 14) * cyClient / 14) ;

           aptl[3].x = cxClient ;
           aptl[3].y = i * cyClient / 14 ;

           aptl[4].x = cxClient ;
           aptl[4].y = (i + 1) * cyClient / 14 ;

           aptl[5].x = 2 * cxClient / 3 ;
           aptl[5].y = max (0L, (2 * (i + 1) - 14) * cyClient / 14) ;

           aptl[6].x = cxClient / 3 ;
           aptl[6].y = min (cyClient, 2 * (i + 1) * cyClient / 14) ;

           aptl[7].x = 0 ;
           aptl[7].y = (i + 1) * cyClient / 14 ;

           GpiSetColor (hps, lColors[i % 7]) ;
           GpiBeginArea (hps, BA_BOUNDARY | BA_ALTERNATE) ;

           GpiMove (hps, aptl) ;                        // Splines
           GpiPolySpline (hps, 3L, aptl + 1) ;
           GpiLine (hps, aptl + 4) ;
           GpiPolySpline (hps, 3L, aptl + 5) ;
           GpiLine (hps, aptl) ;

           GpiEndArea (hps) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 VF15.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\VECTFONT\VF15.C

 /*--------------------------
    VF15.C -- Clipped Spokes
   --------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <math.h>
 #include "vectfont.h"

 VOID Display_ModSpokes (HPS hps, LONG cxClient, LONG cyClient)
      {
      static CHAR szText[] = "WOW" ;
      static LONG cbText = sizeof szText - 1 ;
      static LONG lColors[] = { CLR_BLUE, CLR_GREEN, CLR_CYAN,
                                CLR_RED,  CLR_PINK,  CLR_YELLOW,
                                CLR_WHITE } ;
      double      dMaxRadius ;
      INT         i, iNumColors = sizeof lColors / sizeof lColors[0] ;
      POINTL      ptl ;

      CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
      GpiSetCharSet (hps, LCID_MYFONT) ;
      ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
      QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;

      ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;

      GpiBeginPath (hps, ID_PATH) ;
      GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
      GpiEndPath (hps) ;

      GpiSetLineWidthGeom (hps, 6L) ;                   // 1/12 inch
      GpiModifyPath (hps, ID_PATH, MPATH_STROKE) ;
      GpiSetClipPath (hps, ID_PATH, SCP_AND | SCP_ALTERNATE) ;

      dMaxRadius = sqrt (pow (cxClient / 2.0, 2.0) +
                         pow (cyClient / 2.0, 2.0)) ;
                                                        // Draw spokes
      for (i = 0 ; i < 360 ; i++)
           {
           GpiSetColor (hps, lColors[i % iNumColors]) ;

           ptl.x = cxClient / 2 ;
           ptl.y = cyClient / 2 ;
           GpiMove (hps, &ptl) ;

           ptl.x += (LONG) (dMaxRadius * cos (i * 6.28 / 360)) ;
           ptl.y += (LONG) (dMaxRadius * sin (i * 6.28 / 360)) ;
           GpiLine (hps, &ptl) ;
           }
      GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
      GpiDeleteSetId (hps, LCID_MYFONT) ;
      }


 WINDOW.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\SPY\WINDOW.C

 /***************************************************************************\
 * window.c - Spy Window dialog functions
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/

 #define        INCL_WINDIALOGS
        INCL_WINHEAP                        /* needed for spy.h */
 #define        INCL_WININPUT
 #define        INCL_WINLISTBOXES
 #define        INCL_WINMESSAGEMGR
        INCL_WINPOINTERS                /* needed for spy.h */
 #define        INCL_WINWINDOWMGR
 #include <os2.h>
 #include <stdio.h>
 #include <string.h>
 #include "spy.h"
 #include "spyhook.h"
 #include <time.h>
 #include <stdlib.h>


 /* Forward Declarations */
 void    InitWindowList(HWND, HWND, int);
 void    BuildWindowWatchList(void);
 void    DisplayWindowInfo(HWND, HWND);
 void    SelectWindowFromText(HWND);
 SHORT   DumpWindowInfo(HWND, SHORT);
 int cdecl    CompareHwnds(const void *, const void *);
 MRESULT        EXPENTRY SpyWindowsDlgProc(HWND, USHORT, MPARAM, MPARAM);




 /***************************************************************************\
 * MRESULT EXPENTRY SpyWindowsDlgProc(hwnd, msg, mp1, mp2)
 *
 * The Spy Windows Dialog procedure
 \***************************************************************************/
 MRESULT EXPENTRY SpyWindowsDlgProc(hwnd, msg, mp1, mp2)
 HWND hwnd;
 USHORT msg;
 MPARAM mp1;
 MPARAM mp2;
 {
     BOOL        fSelect = TRUE;
     SHORT       cWindows;
     HWND        hwndPoint;
     HWND        hwndItem;   /* from handle of list item */
     USHORT      iItemFocus; /* Index to item that has the focus */

     switch (msg) {

     case WM_INITDLG:
         /* Initialize the dialog items */
         hwndWindowLB = WinWindowFromID(hwnd, DID_WINDOWLIST);
         InitWindowList(hwnd, HWND_DESKTOP, 0);
         InitWindowList(hwnd, HWND_OBJECT, -10);
         hwndWinDlgDisp = NULL;
         fTrackingListBox = TRUE;
         break;

     case WM_CHAR:
         /*
          * Handle VK_ENTER and VK_NEWLINE if our Edit control has
          * the focus and it is a keydown
          */
         if (!(SHORT1FROMMP(mp1) & KC_KEYUP) &&
                 (SHORT1FROMMP(mp1) & KC_VIRTUALKEY) &&
                 ( (SHORT2FROMMP(mp2) == VK_ENTER) ||
                   (SHORT2FROMMP(mp2) == VK_NEWLINE) )) {


             if (WinQueryFocus(HWND_DESKTOP, FALSE) ==
                     WinWindowFromID(hwnd, DID_WHANDLE)) {
                 SelectWindowFromText(hwnd);
                 break;
             }
         }

         /* Normaly pass to dialog procedure to handle message */
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
         break;

     case WM_COMMAND:
         switch (SHORT1FROMMP(mp1)) {
         case DID_OK:
             BuildWindowWatchList();
         case DID_CANCEL:
             /* Now dismiss the dialog */
             WinDismissDlg(hwnd, SHORT1FROMMP(mp1));
             break;
         case DID_WUNSELALL:
             fSelect = FALSE;
         case DID_WSELALL:
             cWindows = SHORT1FROMMR( WinSendMsg(hwndWindowLB, LM_QUERYITEMCOU
                 0L, 0L));

             fTrackingListBox = FALSE;
             while (cWindows) {
                 /* Loop through all windows, selecting or unselcting all */
                 WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)--cWindows,
                     (MPARAM)fSelect);
             }
             fTrackingListBox = TRUE;
             break;

         case DID_WSELMOUSE:
             /* Call function to track mouse, returns window handle */
             hwndPoint = HwndSelWinWithMouse(hwnd, DisplayWindowInfo);
             if (hwndPoint == NULL)
         break;    /* No window to process */

             /*
              * Now find the window in the list, Make the item visible
              * and set the item as selected.
              */
             cWindows = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYITEMCOUN
                     0L, 0L));

             while (cWindows) {
                 /*
                  * Loop through all windows until we wind the right
                  * one with the correct window handle
                  */
                 hwndItem = (HWND)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                         (MPARAM)--cWindows, 0L);

                 if (hwndItem == hwndPoint) {
                     /* found the right item, move it to top */
                     WinSendMsg(hwndWindowLB, LM_SETTOPINDEX, (MPARAM)cWindows
                     WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)cWindows,
                             (MPARAM)TRUE);
                     break;
                 }
             }
             break;

         }
         break;


     default:
         /*
          * Default is to see if the listbox has changed its focus
          * item number.  If it has, then we want to display the information
          * about the window that the listbox cursor is over.  There is no
          * legal way to do this, One approach appears to temporary set the
          * listbox to be a single selection listbox, then query its selection
          * and set it back into multiple selection mode.
          */
         if (fTrackingListBox && hwndWindowLB != NULL) {

             WinSetWindowBits(hwndWindowLB, QWL_STYLE, 0L, LS_MULTIPLESEL);
             iItemFocus = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYSELECT
                     (MPARAM)LIT_FIRST, 0L));
             WinSetWindowBits(hwndWindowLB, QWL_STYLE, LS_MULTIPLESEL,
                     LS_MULTIPLESEL);

             if (iItemFocus != iCurItemFocus) {
                 iCurItemFocus = iItemFocus;
                 if (iItemFocus != (USHORT)-1) {

                     hwndItem = (HWND)WinSendMsg(hwndWindowLB, LM_QUERYITEMHAN
                             (MPARAM)iItemFocus, 0L);
                     DisplayWindowInfo(hwnd, hwndItem);
                 }
             }
         }
         return(WinDefDlgProc(hwnd, msg, mp1, mp2));
     }
     return 0L;
 }




 /***************************************************************************\
 * void SelectWindowFromText(hwndDlg)
 *
 *   Updates the text that is displayed in the message text line
 \***************************************************************************/
 void SelectWindowFromText(hwndDlg)
 HWND    hwndDlg;
 {
     char    szTemp[80];
     HWND    hwndSelect;
     SHORT   cItems;
     SHORT   i;


     /* First get the edit text from the string */
     WinQueryDlgItemText(hwndDlg, DID_WHANDLE, sizeof(szTemp),
             (PSZ)szTemp);

     hwndSelect = (HWND)UConvertStringToNum(szTemp);

     cItems =SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYITEMCOUNT,
             0L, 0L));

     for (i=0; i < cItems; i++) {
         if ((HWND)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                 (MPARAM)i, 0L) == hwndSelect)
             break;  /* found it */
     }

     if (i < cItems) {
         /*
          * found the hwnd, bring to top, and select it
          */
         WinSendMsg(hwndWindowLB, LM_SETTOPINDEX,
                 MPFROMSHORT(i),  (MPARAM)0L);

         /* Always set it on */
         WinSendMsg(hwndWindowLB, LM_SELECTITEM,
                 MPFROMSHORT(i), MPFROMSHORT(TRUE));

     } else {
         WinAlarm(HWND_DESKTOP, WA_WARNING);
         WinSetDlgItemText(hwndDlg, DID_WHANDLE, (PSZ)"");
     }

 }


 /****************************************************************************
 * InitWindowList (hwndDialog, hwnd, level)
 *
 * Builds the list of windows displayed in the windows dialog
 \***************************************************************************/
 void InitWindowList(hwndDialog, hwnd, level)
 HWND    hwndDialog;
 HWND    hwnd;
 int     level;
 {
     char    szTemp[30];
     char    szId[20];
     HWND    hwndT;
     USHORT  item;
     USHORT  id;
     int     i;

     /*
      * We will first add this item to our list of
      * items in the listbox, If the item is in our list of hwnds,
      * set the item selected. To keep from getting into endless loops
      * will not add spywindow client, and descendants.
      */
     if (hwnd != hwndSpy) {
         id = (USHORT)WinQueryWindowUShort(hwnd, QWS_ID);
         sprintf(szId, "ID: %x", id);

         for (i = 0; i < cToName; i++) {
             if (id == rgidtoname[i].id) {
                 strcpy (szId, rgidtoname[i].szIdName);
                 break;
             }
         }

         sprintf(szTemp, "%04x(%d) - %s", (USHORT)(ULONG)hwnd, level, szId);
         item = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_INSERTITEM,
                 (MPARAM)LIT_END, (MPARAM)(PSZ)szTemp));

         /* Set the item handle to the handle of the window */
         WinSendMsg(hwndWindowLB, LM_SETITEMHANDLE, (MPARAM)item,
                 (MPARAM)hwnd);

         if (SpyFWindowInList(hwnd, TRUE))
             WinSendMsg(hwndWindowLB, LM_SELECTITEM, (MPARAM)item,
                 (MPARAM)TRUE);

         /*
          * Then we recurse with all of our children
          */
         if ((hwndT = WinQueryWindow(hwnd, QW_TOP, FALSE)) != NULL)
             InitWindowList(hwndDialog, hwndT, level+1);
     }

     /*
      * Then go to our next sibling
      */
     if ((hwndT = WinQueryWindow(hwnd, QW_NEXT, FALSE)) != NULL)
         InitWindowList(hwndDialog, hwndT, level);
 }



 /***************************************************************************\
 * BuildWindowWatchList()
 *
 * Updates the list of windows to be watched from the listbox
 \***************************************************************************/
 void BuildWindowWatchList(void)
 {

     USHORT  itemPrevious;
     USHORT  item;
     HWND    hwnd;

     SHORT   chwnd;
     HWND    rghwnd[MAXHWNDS];

     /*
      * Simply loop through asking for the next selected item in the
      * list.  Make sure not to overrun our list.
      */
     itemPrevious = (USHORT)LIT_FIRST;
     chwnd = 0;

     while ((item = SHORT1FROMMR(WinSendMsg(hwndWindowLB, LM_QUERYSELECTION,
             (MPARAM)itemPrevious, 0L))) != (USHORT)LIT_NONE) {
         /*
          * Get the items handle, which has the value of the window handle
          */
         hwnd = (HWND)WinSendMsg(hwndWindowLB, LM_QUERYITEMHANDLE,
                 (MPARAM)item, 0L);

         rghwnd[chwnd++] = hwnd;
         if (chwnd >= MAXHWNDS)
             break;  /* Dont overflow array */
         itemPrevious = item;    /* Where to cointinue the search */
     }

     SpySetWindowList (chwnd, rghwnd);
 }


 /***************************************************************************\
 * HWND HwndSelWinWithMouse(HWND hwnd, void (*pfnDisplayInfo)(HWND, HWND))
 *
 *             This function is used to allow the user to select a window with
 *           the mouse.  If fDisplayInfo is TRUE, it will update the
 *           information in the dialog box, about the window that the
 *           mouse is currently over.
 \***************************************************************************/
 HWND HwndSelWinWithMouse(hwnd, pfnDisplayInfo)
 HWND hwnd;
 void (*pfnDisplayInfo)(HWND, HWND);
 {

     QMSG        qmsg;
     HWND        hwndPoint;
     char        szClassName[50];    /* Class name of window */
     CLASSINFO   classinfo;          /* Information about class */


     /*
      * First set the capture to the specified window
      */
     WinSetCapture(HWND_DESKTOP, hwnd);
     WinSetPointer (HWND_DESKTOP, hptrSelWin);

     /*
      * Now loop through all of the messages that are sent, until
      * we get our mouse 1 down message.  We will also filter out
      * the WM_MOVE message, else we will dispatch the messages.
      */
     while (WinGetMsg(hab, &qmsg, NULL, 0, 0)) {
         if (qmsg.msg == WM_MOUSEMOVE) {
             if (pfnDisplayInfo != NULL) {
                 hwndPoint = WinWindowFromPoint(HWND_DESKTOP,
                     &qmsg.ptl, TRUE, FALSE);
                 (*pfnDisplayInfo)(hwnd, hwndPoint);
             }
         }
         else if (qmsg.msg == WM_BUTTON1DOWN)
             break;
         else
             WinDispatchMsg(hab, &qmsg);
     }

     WinSetPointer (HWND_DESKTOP, hptrArrow);
     WinSetCapture(HWND_DESKTOP, NULL);


     /*
      * Map the point to the window,  If the CTRL-Key is down,
      * we will go up through the parent chain until we get to
      * a frame window or desktop.  Dont let hwndSpy through!!!
      */
     hwndPoint = WinWindowFromPoint(HWND_DESKTOP,
         &qmsg.ptl, TRUE, FALSE);
     if (WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000) {
         /* Asked for frame window */
         for (;;) {
             if (hwndPoint == NULL)
                 return (NULL);         /* No frames available */
             /* See if frame class */
             WinQueryClassName(hwndPoint, sizeof(szClassName),
                 (PSZ)szClassName);
             if (WinQueryClassInfo(hab, (PSZ)szClassName, &classinfo) &&
                     (classinfo.flClassStyle & CS_FRAME))
                 break;  /* We have our frame */

             /* Not frame, go back to parent */
             hwndPoint = WinQueryWindow(hwndPoint, QW_PARENT, FALSE);
         }
     }

     if (pfnDisplayInfo != NULL)
         (*pfnDisplayInfo)(hwnd, hwndPoint);

     if (WinIsChild(hwndPoint, hwndSpy))
         return (NULL);    /* Dont want to get in endless loops */

     return (hwndPoint);
 }



 /***************************************************************************\
 * DisplayWindowInfo(HWND hwndDialog, HWND hwnd)
 *
 * Displays the information about the selected window in the dialog
 \***************************************************************************/
 void DisplayWindowInfo(hwndDlg, hwnd)
 HWND            hwndDlg;
 HWND            hwnd;
 {
     HWND        hwndT;
     HWND        hwndParent;
     char        szTemp[50];
     char        szTemp2[10];
     CLASSINFO   classinfo;
     RECTL       rcl;
     USHORT      id;
     ULONG       ul;
     USHORT      us1;
     USHORT      us2;
     USHORT      us3;
     USHORT      us4;
     PID         pidWindow;
     TID         tidWindow;


     if (hwnd != hwndWinDlgDisp)
     {
         hwndWinDlgDisp = hwnd;

         /* This could be table driven */
         sprintf(szTemp, "0x%04x", (SHORT)(ULONG) hwnd);
         WinSetDlgItemText(hwndDlg, DID_WHANDLE, (PSZ)szTemp);

         WinQueryClassName(hwnd, sizeof(szTemp), (PSZ)szTemp);
         if (!WinQueryClassInfo(hab, (PSZ)szTemp, &classinfo)) {
             classinfo.flClassStyle = -1;    /* Let know error conditon */
             classinfo.cbWindowData = 0;     /* Make sure we dont dump */
         }

         WinSetDlgItemText(hwndDlg, DID_WCLASS, (PSZ)szTemp);


         /*
          * Warning, we only query the text if the window is not an object
          * window.  If it is an object window, the message queue may not
          * be processing messages, which could hang us
          */
         if (WinIsChild(hwnd, HWND_OBJECT))
             szTemp[0] = '\0';   /* No text available */
         else
             WinQueryWindowText(hwnd, sizeof(szTemp), (PSZ)szTemp);
         WinSetDlgItemText(hwndDlg, DID_WTEXT, (PSZ)szTemp);

         hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         sprintf(szTemp, "0x%04x", (SHORT)(LONG)hwndParent);
         WinSetDlgItemText(hwndDlg, DID_WPARENT, (PSZ)szTemp);

         hwndT = WinQueryWindow(hwnd, QW_TOP, FALSE);
         sprintf(szTemp, "0x%04x", (SHORT)(LONG) hwndT);
         WinSetDlgItemText(hwndDlg, DID_WCHILD, (PSZ)szTemp);

         hwndT = WinQueryWindow(hwnd, QW_OWNER, FALSE);
         sprintf(szTemp, "0x%04x", (SHORT)(LONG) hwndT);
         WinSetDlgItemText(hwndDlg, DID_WOWNER, (PSZ)szTemp);

         WinQueryWindowRect(hwnd, &rcl);
         WinMapWindowPoints(hwnd, hwndParent, (PPOINTL)&rcl, 2);
         sprintf(szTemp, "(%d, %d), (%d, %d)", (SHORT)rcl.xLeft,
             (SHORT)rcl.yBottom, (SHORT)rcl.xRight, (SHORT)rcl.yTop);
         WinSetDlgItemText(hwndDlg, DID_WRECT, (PSZ)szTemp);

         id = (USHORT)WinQueryWindowUShort(hwnd, QWS_ID);
         sprintf(szTemp, "0x%04x", id);
         WinSetDlgItemText(hwndDlg, DID_WID, (PSZ)szTemp);

         ul = (ULONG)WinQueryWindowULong(hwnd, QWL_STYLE);
         sprintf(szTemp, "0x%08lx", ul);
         WinSetDlgItemText(hwndDlg, DID_WSTYLE, (PSZ)szTemp);

         sprintf(szTemp, "0x%08lx", classinfo.flClassStyle);
         WinSetDlgItemText(hwndDlg, DID_WCSTYLE, (PSZ)szTemp);

         ul = (ULONG)WinQueryWindowULong(hwnd, QWP_PFNWP);
         sprintf(szTemp, "%p", ul);
         WinSetDlgItemText(hwndDlg, DID_WPFNWP, (PSZ)szTemp);

         ul = (ULONG)WinQueryWindowULong(hwnd, QWL_HMQ);
         sprintf(szTemp, "0x%04x", (SHORT)ul);
         WinSetDlgItemText(hwndDlg, DID_WHMQ, (PSZ)szTemp);

         WinQueryWindowProcess(hwnd, &pidWindow, &tidWindow);
         sprintf(szTemp, "%d", (SHORT)pidWindow);
         WinSetDlgItemText(hwndDlg, DID_WPID, (PSZ)szTemp);
         sprintf(szTemp, "%d", (SHORT)tidWindow);
         WinSetDlgItemText(hwndDlg, DID_WTID, (PSZ)szTemp);

         /*
          * We have four General purpose lines left, used only for
          * frames now
          */
         if ((classinfo.flClassStyle & CS_FRAME) &&
                     (classinfo.cbWindowData > QWL_HWNDFOCUSSAVE)) {
             ul = (ULONG)WinQueryWindowULong(hwnd, QWL_HWNDFOCUSSAVE);
             sprintf(szTemp, "Frame Focus: %p",ul);
             WinSetDlgItemText(hwndDlg, DID_WOLINE1, (PSZ)szTemp);

             us1 = (USHORT)WinQueryWindowUShort(hwnd, QWS_FLAGS);
             us2 = (USHORT)WinQueryWindowUShort(hwnd, QWS_RESULT);
             sprintf(szTemp, "Flags: 0x%04x Rslt: 0x%04x", us1, us2);
             WinSetDlgItemText(hwndDlg, DID_WOLINE2, (PSZ)szTemp);

             us1 = (USHORT)WinQueryWindowUShort(hwnd, QWS_XRESTORE);
             us2 = (USHORT)WinQueryWindowUShort(hwnd, QWS_YRESTORE);
             us3 = (USHORT)WinQueryWindowUShort(hwnd, QWS_CXRESTORE);
             us4 = (USHORT)WinQueryWindowUShort(hwnd, QWS_CYRESTORE);
             sprintf(szTemp, "Restore: (%d, %d, %d, %d)",us1, us2, us3, us4);
             WinSetDlgItemText(hwndDlg, DID_WOLINE3, (PSZ)szTemp);

             us1 = (USHORT)WinQueryWindowUShort(hwnd, QWS_XMINIMIZE);
             us2 = (USHORT)WinQueryWindowUShort(hwnd, QWS_YMINIMIZE);
             sprintf(szTemp, "Minimize: (%d, %d)",us1, us2);
             WinSetDlgItemText(hwndDlg, DID_WOLINE4, (PSZ)szTemp);
         } else {
             /*
              * Nothing special to output for this window type, so lets
              * dump the window extra words.
              * Note: This code is sh.ty
              */
             us1 = 0;    /* Word offset */
             for (id=DID_WOLINE1; id <= DID_WOLINE4; id++) {
                 szTemp[0] = '\0';
                 for (us2 = 0; us2 < 4; us2++) {
                     if (us1 >= classinfo.cbWindowData)
                         break;
                     us3 = (USHORT)WinQueryWindowUShort(hwnd, us1);
                     sprintf(szTemp2, "%04x ", us3);
                     strcat(szTemp, szTemp2);
                     us1 += 2;   /* Setup for next word */
                 }

                 /* output this line */
                 WinSetDlgItemText(hwndDlg, id, (PSZ)szTemp);
             }
         }
     }
 }




 /***************************************************************************\
 * DumpOneWindowInfo()
 *
 * Dump the information about one window to the current outputs
 \***************************************************************************/
 void    DumpOneWindowInfo()
 {
     HWND        hwndPoint;
     HWND        hwndT;
     SHORT       wLevel;

     hwndPoint = HwndSelWinWithMouse(hwndSpy, NULL);
     if (hwndPoint == NULL)
         return;    /* No window selected */

     /* Now see what level the window is at */
     wLevel = 0;
     hwndT = hwndPoint;
     while (hwndT != NULL) {
         wLevel++;
         hwndT = WinQueryWindow(hwndT, QW_PARENT, FALSE);
     };


     DumpWindowInfo(hwndPoint, wLevel);
 }


 /****************************************************************************
 * DumpAllWIndowsInfo (HWND hwnd, WORD wLevel)
 *
 * Dumps the complet window list out to the current output units.
 \***************************************************************************/
 SHORT DumpAllWindowsInfo(hwnd, wLevel)
 HWND    hwnd;
 SHORT   wLevel;
 {
     HWND    hwndT;
     SPWD    *pspwdT;
     SHORT   cWindowBytes;

     pspwdT = pspwd + wDumpCount;

     cWindowBytes = DumpWindowInfo(hwnd, wLevel);

     pspwdT->hwnd = hwnd;
     pspwdT->index = wDumpCount;


     /*
      * Then we recurse with all of our children
      */
     if ((hwndT = WinQueryWindow(hwnd, QW_TOP, FALSE)) != NULL)
         cWindowBytes += DumpAllWindowsInfo(hwndT, wLevel+1);

     /*
      * Then go to our next sibling
      */
     if ((hwndT = WinQueryWindow(hwnd, QW_NEXT, FALSE)) != NULL)
         cWindowBytes += DumpAllWindowsInfo(hwndT, wLevel);

     return (cWindowBytes);
 }



 /****************************************************************************
 * DumpWindowIndex (void)
 *
 * Dump a sorted list of Hwnds and index into other list
 \***************************************************************************/
 void    DumpWindowIndex(cBytes)
 SHORT   cBytes;
 {
     SHORT   cch;
     char    szTemp[20];
     char    szOutput[100];
     SPWD    *pspwdT;
     SHORT   i;

     /* Sort the hwnds first */
     qsort((void *)pspwd, wDumpCount, sizeof(SPWD), CompareHwnds);
     pspwdT = pspwd;

     strcpy (szOutput, "Index of Window Handles");
     cch = strlen(szOutput);
     for (i=0; i< wDumpCount; i++) {
         if ((i & 3) == 0) {
             /* 4 per row */
             OutputString(szOutput, cch);
             szOutput[0] = '\0';
             cch = 0;
         }

         cch += sprintf(szTemp, "%3d-%04x ",
                 pspwdT->index,    (USHORT)(ULONG) pspwdT->hwnd);
         strcat (szOutput, szTemp);
         pspwdT++;
     }

     OutputString(szOutput, cch);

     cch = sprintf(szOutput, "Number of Windows: %d, Approx heap size: %d",
             wDumpCount, cBytes);
     OutputString(szOutput, cch);


 }


 /****************************************************************************
 * int  CompareHwnds(SPWD *pspwd1, SPWD *pspwd2)
 *
 * Compares two window handles
 \***************************************************************************/
 int  cdecl CompareHwnds(pspwd1, pspwd2)
 const void    *pspwd1;
 const void    *pspwd2;
 {
     return (((SPWD *)pspwd1)->hwnd < ((SPWD *)pspwd2)->hwnd)? -1 : 1;
 }




 /***************************************************************************\
 * DumpWindowInfo(HWND hwnd, SHORT wLevel)
 *
 * Displays the information about the selected window in the dialog
 \***************************************************************************/
 SHORT DumpWindowInfo(hwnd, wLevel)
 HWND        hwnd;
 SHORT       wLevel;
 {
     HWND    hwndParent;
     HWND    hwndChild;
     HWND    hwndOwner;

     char    szTemp[100];
     char    szTemp2[20];
     SHORT   cch;
     char    szClass[30];
     RECTL   rcl;
     USHORT  id;
     ULONG   ulStyle;
     ULONG   ulPFNWP;
     ULONG   ulHMQ;
     SHORT   wOffsetClassData;
     SHORT   wWindowWord;
     PID     pidWindow;
     TID     tidWindow;

     CLASSINFO   classinfo;

     hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
     hwndChild = WinQueryWindow(hwnd, QW_TOP, FALSE);
     hwndOwner = WinQueryWindow(hwnd, QW_OWNER, FALSE);
     id = (USHORT)WinQueryWindowUShort(hwnd, QWS_ID);
     ulHMQ = (ULONG)WinQueryWindowULong(hwnd, QWL_HMQ);
     WinQueryWindowRect(hwnd, &rcl);
     WinMapWindowPoints(hwnd, hwndParent, (PPOINTL)&rcl, 2);

     cch = sprintf(szTemp,
         "%d-H:%04x(%d) P:%04x C:%04x O:%04x ID:%04x MQ:%04x (%d, %d) (%d, %d)
         ++wDumpCount, (SHORT)(LONG)hwnd, wLevel, (SHORT)(LONG)hwndParent,
         (SHORT)(LONG)hwndChild, (SHORT)(LONG)hwndOwner, id, (SHORT)ulHMQ,
         (SHORT)rcl.xLeft, (SHORT)rcl.yBottom,
         (SHORT)rcl.xRight, (SHORT)rcl.yTop);

     OutputString(szTemp, cch);

     ulStyle = (ULONG)WinQueryWindowULong(hwnd, QWL_STYLE);
     ulPFNWP = (ULONG)WinQueryWindowULong(hwnd, QWP_PFNWP);
     WinQueryClassName(hwnd, sizeof(szClass), (PSZ)szClass);
     if (!WinQueryClassInfo(hab, (PSZ)szClass, &classinfo)) {
         classinfo.flClassStyle = -1;    /* Let know error conditon */
         classinfo.cbWindowData = 0;     /* Make sure we dont dump */
     }

     WinQueryWindowProcess(hwnd, &pidWindow, &tidWindow);

     cch = sprintf(szTemp,
         "          St:%08lx  PID:%d  TID:%d  Pfn:%p  Cl:%s",
         ulStyle, pidWindow, tidWindow, ulPFNWP, szClass);
     OutputString(szTemp, cch);

     /*
      * Dump the window extra words out also.
      */
     strcpy (szTemp, "          ");
     id = 8;
     for (wOffsetClassData = 0; wOffsetClassData < (SHORT) classinfo.cbWindowD
             wOffsetClassData += 2) {

         wWindowWord = (USHORT)WinQueryWindowUShort(hwnd, wOffsetClassData);
         sprintf(szTemp2, "%04x ", wWindowWord);
         strcat(szTemp, szTemp2);
         if (--id == 0) {
             /* line full is full */
             OutputString(szTemp, strlen(szTemp));
             szTemp[10] = '\0';
             id = 8;
         }
     }

     if (id != 8)
         OutputString(szTemp, strlen(szTemp));

     /* Return the number of bytes associated with the window */
     return ((SIZEOFWND + classinfo.cbWindowData + 3) & 0xfffc);
 }




 /***************************************************************************\
 * SelOrDeselWithMouse(BOOL fSelect)
 *
 * Fastway to add/or remove window from watch list
 \***************************************************************************/

 void SelOrDeselWithMouse(fSelect)
 BOOL    fSelect;
 {
     HWND    rghwnd[MAXHWNDS];
     HWND    hwndPoint;
     SHORT    chwnd;
     BOOL    fWinCurInList;
     SHORT    i;

     /* First get the window of interest */
     hwndPoint = HwndSelWinWithMouse(hwndSpy, NULL);
     if (hwndPoint == NULL)
         return;    /* No window selected */
     fWinCurInList = SpyFWindowInList(hwndPoint, TRUE);

     if ((fWinCurInList && fSelect)
             || (!fWinCurInList && !fSelect))
         return;        /* Alredy right state */

     chwnd = SpyGetWindowList(MAXHWNDS, (HWND FAR *)rghwnd);

     if (fSelect) {
         /* Add window to end of list */
         rghwnd[chwnd++] = hwndPoint;
     } else {
         /* find it in the list, and delete it out */
         for (i=0; rghwnd[i] != hwndPoint; i++)
             ;

         /* Now copy rest of them down */
         chwnd--;    /* One less item */
         for (;i < chwnd; i++ ) {
             rghwnd[i] = rghwnd[i+1];
         }
     }

     /* Now call to update the list */
     SpySetWindowList(chwnd, (HWND FAR *)rghwnd);
 }




 WMCHAR.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\WMCHAR\WMCHAR.C

 /***************************************************************************\
 * wmchar.c -- Displays WM_CHAR messages
 *
 * Created by Microsoft Corporation, 1989
 \***************************************************************************/


 #define INCL_WIN
 #define INCL_GPILCIDS
 #include <os2.h>
 #include <string.h>
 #include <stdio.h>
 #include "wmchar.h"


 #define max2(a, b)  ((a) > (b) ? (a) : (b))
 #define min2(a, b)  ((a) < (b) ? (a) : (b))


 /* display dependent values */
 SHORT gcyChar;
 SHORT gcxAveChar;
 SHORT gcxMaxChar;
 SHORT gcxMargin;

 /* program globals */
 USHORT gcMessages = 0;      /* also the "number" of the most recent message *
 CHAR gszNull[] = "  --";    /* denotes an empty field */
 BOOL gfKeyUps = FALSE;


 /* char messages are stored in a circular buffer */
 typedef struct _CHMSG {     /* cm */
     USHORT usKC;
     UCHAR uchRep;
     UCHAR uchScan;
     USHORT ch;
     USHORT usVK;
 } CHMSG;

 #define CBUFFERELTS 20

 CHMSG gacm[CBUFFERELTS];
 SHORT gicmStart = 0;


 /* display field attributes are stored in an array */
 typedef struct _MSGFIELD {  /* mf */
     BOOL fDisplay;          /* TRUE if field is currently being displayed */
     SHORT cxWidth;          /* field width, initially chars, then pixels */
     CHAR *szHeading;        /* field heading */
 } MSGFIELD;

 #define INUMBER         0
 #define IVIRTUALKEY     1
 #define ICHAR           2
 #define ISCANCODE       3
 #define IREPEAT         4
 #define IFLAGS          5

 MSGFIELD gamf[CDISPLAYFIELDS] = {
     TRUE,   7, " #",
     TRUE,  15, "VK_ Value",
     TRUE,  14, "Char",
     FALSE, 10, "Scan",
     FALSE, 8,  "Rep",
     TRUE,  0,  "KC_ Flags"      /* variable width, so it's last */
 };

 SHORT gcxMargin = 3;


 /* char flag strings */
 CHAR *gachFlags[] = {
     "char",
     "virtualkey",
     "scancode",
     "shift",
     "ctrl",
     "alt",
     "keyup",
     "prevdown",
     "lonekey",
     "deadkey",
     "composite",
     "invalidcomp",
     "toggle",
     "invalidchar",
     "dbcsrsrvd1",
     "dbcsrsrvd2"
 };

 /* virtual key strings */
 CHAR *gachVK[] = {
     gszNull,
     "button1",
     "button2",
     "button3",
     "break",
     "backspace",
     "tab",
     "backtab",
     "newline",
     "shift",
     "ctrl",
     "alt",
     "altgraf",
     "pause",
     "capslock",
     "esc",
     "space",
     "pageup",
     "pagedown",
     "end",
     "home",
     "left",
     "up",
     "right",
     "down",
     "printscrn",
     "insert",
     "delete",
     "scrllock",
     "numlock",
     "enter",
     "sysrq",
     "f1",
     "f2",
     "f3",
     "f4",
     "f5",
     "f6",
     "f7",
     "f8",
     "f9",
     "f10",
     "f11",
     "f12",
     "f13",
     "f14",
     "f15",
     "f16",
     "f17",
     "f18",
     "f19",
     "f20",
     "f21",
     "f22",
     "f23",
     "f24"
 };



 /***************************************************************************\
 * DrawText
 *
 * This function displays a text string.
 *
 * Parameters:
 *   hps:        presentation space
 *   cx, cy:     point to begin drawing
 *   sz:         string to draw
 *   iColor:     text color
 *   fExtent:    whether to calc extent or not
 *
 * Returns:
 *   x position of end of painted string if fExtent == TRUE
 \***************************************************************************/

 SHORT DrawText(
     HPS hps,
     SHORT cx,
     SHORT cy,
     CHAR *sz,
     LONG iColor,
     BOOL fExtent)
 {
     RECTL rcl;
     SHORT cch;

     cch = strlen(sz);

     rcl.xLeft = cx;
     rcl.xRight = cx + cch * gcxMaxChar;     /* hack */
     rcl.yBottom = cy;
     rcl.yTop = cy + gcyChar;

     WinDrawText(hps, cch, (PCH)sz, (PRECTL)&rcl, iColor, SYSCLR_WINDOW, 0);

     if (fExtent) {
         WinDrawText(hps, cch, (PCH)sz, (PRECTL)&rcl, iColor,
                     SYSCLR_WINDOW, DT_QUERYEXTENT);
         return (SHORT)rcl.xRight;
     }

     return 0;
 }



 /***************************************************************************\
 * FormatHexChar
 *
 * This function formats an unsigned hex number into a string.  It is needed
 * to get around shortcomings of sprintf().
 *
 * Parameters:
 *   uch:    number to format
 *   sz:     string to get number
 \***************************************************************************/

 VOID FormatHexChar(
     USHORT uch,
     CHAR *sz)
 {
     sprintf(sz, "0x%2x", uch);

     /* patch up hex byte display */
     if (strlen(sz) > 4) {
         /* remove sign extension of byte */
         sz[2] = sz[4];
         sz[3] = sz[5];
         sz[4] = 0;
     } else if (sz[2] == ' ') {
         /* fill in blank with 0 */
         sz[2] = '0';
     }
 }



 /***************************************************************************\
 * DrawMessage
 *
 * This function draws a single message line.
 *
 * Parameters:
 *   hps:        presentation space
 *   iMessage:   message number to display
 *   pcm:        pointer to message structure
 *   cy:         y position of message
 \***************************************************************************/

 VOID DrawMessage(
     HPS hps,
     SHORT iMessage,
     CHMSG *pcm,
     SHORT cy)
 {
     CHAR sz[16];
     USHORT fsFlags;
     SHORT i;
     SHORT cx;

     cx = gcxMargin;

     if (gamf[INUMBER].fDisplay) {
         /* draw message number */
         sprintf(sz, "%u.", iMessage);
         DrawText(hps, cx, cy, sz, SYSCLR_WINDOWTEXT, FALSE);
         cx += gamf[INUMBER].cxWidth;
     }

     if (gamf[IVIRTUALKEY].fDisplay) {
         /* draw virtual key code */
         DrawText(hps, cx, cy, gachVK[pcm->usVK], SYSCLR_WINDOWTEXT,
                  FALSE);
         cx += gamf[IVIRTUALKEY].cxWidth;
     }

     if (gamf[ICHAR].fDisplay) {
         /* draw character */
         if ((CHAR)pcm->ch) {
             FormatHexChar(pcm->ch, sz);
             DrawText(hps, cx, cy, sz, SYSCLR_WINDOWTEXT, FALSE);
             sprintf(sz, "'%c'", (CHAR)pcm->ch);
             DrawText(hps, cx + 7 * gcxAveChar, cy, sz, SYSCLR_WINDOWTEXT,
                      FALSE);
         } else {
             DrawText(hps, cx, cy, gszNull, SYSCLR_WINDOWTEXT, FALSE);
         }
         cx += gamf[ICHAR].cxWidth;
     }

     if (gamf[ISCANCODE].fDisplay) {
         if (pcm->uchScan) {
             FormatHexChar(pcm->uchScan, sz);
             DrawText(hps, cx, cy, sz, SYSCLR_WINDOWTEXT, FALSE);
         } else {
             DrawText(hps, cx, cy, gszNull, SYSCLR_WINDOWTEXT, FALSE);
         }
         cx += gamf[ISCANCODE].cxWidth;
     }

     if (gamf[IREPEAT].fDisplay) {
         sprintf(sz, "%u", pcm->uchRep);
         DrawText(hps, cx, cy, sz, SYSCLR_WINDOWTEXT, FALSE);
         cx += gamf[IREPEAT].cxWidth;
     }

     if (gamf[IFLAGS].fDisplay) {
         /* draw KC_ flags */
         fsFlags = pcm->usKC;
         for (i = 0; i < 16; i++) {
             if (fsFlags & 1) {
                 switch (i) {
                 case 0:
                     if (gamf[ICHAR].fDisplay)
                         goto DF_PRINTIT;
                     break;
                 case 1:
                     if (gamf[IVIRTUALKEY].fDisplay)
                         goto DF_PRINTIT;
                     break;
                 case 2:
                     if (gamf[ISCANCODE].fDisplay)
                         goto DF_PRINTIT;
                     break;
                 default:
 DF_PRINTIT:
                     cx = DrawText(hps, cx, cy, gachFlags[i], SYSCLR_WINDOWTEX
                     cx += gcxAveChar;
                 }
             }
             fsFlags >>= 1;
         }
         cx += gamf[IFLAGS].cxWidth;
     }
 }



 /***************************************************************************\
 * PaintClient
 *
 * This function paints a client window.
 *
 * Parameters:
 *   hwnd:   window handle
 \***************************************************************************/

 VOID PaintClient(
     HWND hwnd)
 {
     HPS hps;
     RECTL rcl;
     SHORT iMessage;
     SHORT yBottom;
     SHORT cx;
     SHORT i;

     hps = WinBeginPaint(hwnd, (HPS)NULL, (PRECTL)&rcl);

     /* clear invalidated region */
     WinFillRect(hps, (PRECTL)&rcl, SYSCLR_WINDOW);

     /* get y position to start drawing */
     WinQueryWindowRect(hwnd, (PRECTL)&rcl);
     yBottom = (SHORT)(rcl.yTop - gcyChar - gcyChar / 2);

     /* draw headings */
     cx = gcxMargin;
     for (i = 0; i < CDISPLAYFIELDS; i++) {
         if (gamf[i].fDisplay) {
             DrawText(hps, cx, yBottom, gamf[i].szHeading,
                     SYSCLR_WINDOWSTATICTEXT, FALSE);
             cx += gamf[i].cxWidth;
         }
     }

     /* draw each entry */
     i = gicmStart;
     iMessage = gcMessages;
     yBottom -= (gcyChar + gcyChar / 2);
     do {
         if (!iMessage || yBottom < -gcyChar)
             break;

         DrawMessage(hps, iMessage, &gacm[i], yBottom);

         i = (i + 1) % CBUFFERELTS;
         iMessage--;
         yBottom -= gcyChar;

     } while (i != gicmStart);

     WinEndPaint(hps);
 }



 /***************************************************************************\
 * NewMessage
 *
 * This function adds a new WM_CHAR message to the list.
 *
 * Parameters:
 *   hwnd:       window handle
 *   mp1, mp2:
 *   usKC:        key flags
 *   usVK:        virtual key code
 *   ch:         char;
 \***************************************************************************/

 VOID NewMessage(
     HWND hwnd,
     MPARAM mp1,
     MPARAM mp2)
 {
     RECTL rclScroll, rclUpdate;
     HPS hps;

     /* overwrite oldest entry */
     if (gicmStart)
         gicmStart--;
     else
         gicmStart = CBUFFERELTS - 1;

     gacm[gicmStart].usKC = SHORT1FROMMP(mp1);
     gacm[gicmStart].uchRep = CHAR3FROMMP(mp1);
     gacm[gicmStart].uchScan = CHAR4FROMMP(mp1);
     gacm[gicmStart].ch = SHORT1FROMMP(mp2);
     gacm[gicmStart].usVK = SHORT2FROMMP(mp2);

     gcMessages++;

     /* scroll and update intelligently */
     WinQueryWindowRect(hwnd, (PRECTL)&rclScroll);
     rclScroll.yTop -= 2 * gcyChar;
     rclScroll.yBottom = max2(0L, rclScroll.yTop - (LONG)(CBUFFERELTS * gcyCha

     WinScrollWindow(hwnd, 0, -gcyChar, (PRECTL)&rclScroll, (PRECTL)&rclScroll
                     NULL, &rclUpdate, 0);

     hps = WinGetPS(hwnd);
     WinFillRect(hps, (PRECTL)&rclUpdate, SYSCLR_WINDOW);
     DrawMessage( hps
                , gcMessages
                , &gacm[gicmStart]
                , (SHORT)rclScroll.yTop - gcyChar );
     WinReleasePS(hps);
 }



 /***************************************************************************\
 * CheckMenuItem
 *
 * This function sets the check state of a menu item.
 *
 * Parameters:
 *   hwnd:   frame window
 *   id:     menu id
 *   fCheck: check state
 \***************************************************************************/

 VOID CheckMenuItem(
     HWND hwndFrame,
     SHORT id,
     BOOL fCheck)
 {
     HWND hwndMenu;

     /* toggle menu checkmark */
     hwndMenu = WinWindowFromID(hwndFrame, FID_MENU);
     WinSendMsg(hwndMenu, MM_SETITEMATTR, MPFROM2SHORT(id, TRUE),
                MPFROM2SHORT(MIA_CHECKED, fCheck ? MIA_CHECKED : 0));
 }



 /***************************************************************************\
 * MenuCommand
 *
 * This function processes WM_COMMAND messages.
 *
 * Parameters:
 *   hwnd:   client window handle
 *   id:     command identifier
 \***************************************************************************/

 VOID MenuCommand(
     HWND hwnd,
     SHORT id)
 {
     BOOL *pfDisplay;
     HWND hwndFrame;

     if (id == IDM_CLEAR) {

         /* clear current messages */
         gcMessages = 0;
         WinInvalidateRect(hwnd, NULL, FALSE);

     } else if (id == IDM_KEYUPS) {

         /* toggle logging keyups */
         gfKeyUps = !gfKeyUps;
         hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         CheckMenuItem(hwndFrame, IDM_KEYUPS, gfKeyUps);

     } else if (id >= IDM_DISPLAYFIRST && id <= IDM_DISPLAYLAST) {

         /* toggle display field */
         pfDisplay = &(gamf[id - IDM_DISPLAYFIRST].fDisplay);
         *pfDisplay = !*pfDisplay;
         hwndFrame = WinQueryWindow(hwnd, QW_PARENT, FALSE);
         CheckMenuItem(hwndFrame, id, *pfDisplay);

         /* repaint in new format */
         WinInvalidateRect(hwnd, NULL, FALSE);
     }
 }



 /***************************************************************************\
 * MainWndProc
 *
 * This is the window procedure for the main client window.
 \***************************************************************************/

 MRESULT EXPENTRY MainWndProc(
     HWND hwnd,
     USHORT msg,
     MPARAM mp1,
     MPARAM mp2)
 {
     switch (msg) {

     case WM_PAINT:
         PaintClient(hwnd);
         break;

     case WM_COMMAND:
         MenuCommand(hwnd, SHORT1FROMMP(mp1));
         break;

     case WM_CHAR:
         if (gfKeyUps || !(KC_KEYUP & SHORT1FROMMP(mp1))) {

             /* add new message to list */
             NewMessage(hwnd, mp1, mp2);
         }
         break;

     default:
         return WinDefWindowProc(hwnd, msg, mp1, mp2);
     }

     return (MRESULT)0;
 }



 /***************************************************************************\
 * CheckMenus
 *
 * This function sets menu check marks according to their current internal
 * state.
 *
 * Parameters:
 *   hwndFrame:  main app window handle
 \***************************************************************************/

 VOID CheckMenus(
     HWND hwndFrame)
 {
     SHORT id;

     CheckMenuItem(hwndFrame, IDM_KEYUPS, gfKeyUps);

     for (id = IDM_DISPLAYFIRST; id <= IDM_DISPLAYLAST; id++) {
         CheckMenuItem(hwndFrame, id, gamf[id - IDM_DISPLAYFIRST].fDisplay);
     }
 }



 /***************************************************************************\
 * RegisterSwitchEntry
 *
 * This function registers the app with the switch list.
 *
 * Parameters:
 *   hwndFrame:  main app window handle
 \***************************************************************************/

 VOID RegisterSwitchEntry(
     HWND hwndFrame)
 {
     PID pid;
     TID tid;
     SWCNTRL swc;

     WinQueryWindowProcess(hwndFrame, (PPID)&pid, (PTID)&tid);
     WinQueryWindowText(hwndFrame, MAXNAMEL, (PSZ)swc.szSwtitle);
     swc.hwnd = hwndFrame;
     swc.hwndIcon = (ULONG)NULL;
     swc.hprog = (HPROGRAM)NULL;
     swc.idProcess = pid;
     swc.idSession = NULL;
     swc.uchVisibility = SWL_VISIBLE;
     swc.fbJump = SWL_JUMPABLE;
     WinAddSwitchEntry((PSWCNTRL)&swc);
 }



 /***************************************************************************\
 * GetDisplayValues
 *
 * This function sets display dependent global variables.
 \***************************************************************************/

 VOID GetDisplayValues(VOID)
 {
     HPS hps;
     FONTMETRICS fm;
     SHORT i;

     hps = WinGetPS(HWND_DESKTOP);
     GpiQueryFontMetrics(hps, (ULONG)sizeof(FONTMETRICS), (PFONTMETRICS)&fm);
     WinReleasePS(hps);

     gcyChar = (SHORT)(fm.lMaxBaselineExt + fm.lExternalLeading);
     gcxAveChar = (SHORT)fm.lAveCharWidth;
     gcxMaxChar = (SHORT)fm.lMaxCharInc;

     /* adjust display widths for this font */
     gcxMargin *= gcxAveChar;
     for (i = 0; i < CDISPLAYFIELDS; i++) {
         gamf[i].cxWidth *= gcxAveChar;
     }
 }



 /***************************************************************************\
 * main
 *
 * This is the main procedure for the app.
 \***************************************************************************/

 VOID cdecl main(VOID)
 {
     HAB hab;
     HMQ hmq;
     QMSG qmsg;
     ULONG ulCreate;
     HWND hwndFrame;
     HWND hwndClient;
     static CHAR szApp[] = "Char Messages";

     /* init app */
     hab = WinInitialize(0);
     hmq = WinCreateMsgQueue(hab, 0);
     GetDisplayValues();

     /* create main window */
     WinRegisterClass(hab, szApp, MainWndProc, 0L, 0);
     ulCreate = FCF_TITLEBAR | FCF_SYSMENU | FCF_MINMAX | FCF_SIZEBORDER |
                FCF_MENU | FCF_SHELLPOSITION;
     hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, &ulCreate,
                                     szApp, szApp, 0L, (HMODULE) NULL, ID_RESO
                                     &hwndClient);

     /* register with switch list */
     RegisterSwitchEntry(hwndFrame);

     /* initialize menu */
     CheckMenus(hwndFrame);

     /* message loop */
     while (WinGetMsg(hab, &qmsg, NULL, 0, 0))
         WinDispatchMsg(hab, &qmsg);

     /* clean up */
     WinDestroyWindow(hwndFrame);
     WinDestroyMsgQueue(hmq);
     WinTerminate(hab);
 }


 WNDPROC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\CLOCK\WNDPROC.C

 /*
     wndproc.c -- Window Procedure for Clock Client Window
     Created by Microsoft Corporation, 1989
 */
 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "clock.h"
 #include "res.h"

 MRESULT EXPENTRY ClkAboutDlgProc ( HWND , USHORT , MPARAM , MPARAM ) ;
 MRESULT EXPENTRY ClkTicksDlgProc ( HWND , USHORT , MPARAM , MPARAM ) ;
 MRESULT EXPENTRY ClkColorsDlgProc ( HWND , USHORT , MPARAM , MPARAM ) ;

 extern HAB hab ;
 extern HWND hwndFrame ;
 extern BOOL fStartAsIcon ;
 extern VOID ClkDrawFace( HPS );
 extern VOID ClkDrawHand( HPS, SHORT, SHORT );

 VOID ClkPaint ( HWND ) ;
 VOID ClkCreate ( HWND ) ;
 VOID ClkSize ( HWND ) ;
 VOID ClkTimer ( VOID ) ;
 VOID ClkCommand ( HWND , MPARAM ) ;

 VOID ClkHideFrameControls ( HWND ) ;
 VOID ClkShowFrameControls ( HWND ) ;

 SWP swp ;
 HPS hps ;
 HDC hdc ;
 RECTL rclPage ;
 DATETIME dt ;
 BOOL f ;
 BOOL fIconic , fShowSecondHand ;
 BOOL fControlsHidden = FALSE ;
 SIZEL sizl = { 200 , 200 } ;
 ULONG cxRes , cyRes ;

 HWND hwndTitleBar , hwndSysMenu , hwndMinMax , hwndMenu ;


 /*
     ClkWndProc() -- Window Procedure for Clock Client Window
     Returns:  MRESULT (Message Result)
 */
 MRESULT EXPENTRY ClkWndProc ( HWND hwnd , USHORT usMsg ,
                                 MPARAM mp1 , MPARAM mp2 )
 {

     switch ( usMsg ) {

         case WM_TIMER :
             ClkTimer ( ) ;
             return NULL ;

         case WM_PAINT :
             ClkPaint ( hwnd ) ;
             return NULL ;

          case WM_SIZE :
             ClkSize ( hwnd ) ;
             return NULL ;

         case WM_COMMAND :
             ClkCommand ( hwnd , mp1 ) ;
             return NULL ;

         case WM_BUTTON1DOWN :
             return WinSendMsg ( hwndFrame , WM_TRACKFRAME ,
                     ( MPARAM ) ( LOUSHORT ( mp2 ) | TF_MOVE ) , NULL ) ;

         case WM_CHAR:
             if ( fControlsHidden )
                 ClkShowFrameControls ( hwndFrame ) ;
             return NULL ;

         case WM_BUTTON1DBLCLK :
             if ( fControlsHidden )
                 ClkShowFrameControls ( hwndFrame ) ;
             else
                 ClkHideFrameControls ( hwndFrame ) ;
             return NULL ;

         case WM_CREATE :
             ClkCreate ( hwnd ) ;
             return NULL ;

         default :
             /* let default window procedure handle it. */
             return ( WinDefWindowProc ( hwnd , usMsg , mp1 , mp2 ) ) ;
     }
 }


 /*
     ClkCreate() -- Initialize newly created client window
 */
 VOID ClkCreate ( HWND hwnd )
 {
     LONG cxScreen , cyScreen ;        /* screen dimensions */
     LONG xLeft , yBottom ;        /* frame window location */
     LONG cxWidth , cyHeight ;        /* frame window size */
     HWND hwndFrame , hwndMenu ;

     /* we are called before the global hwndFrame is valid */
     hwndFrame = WinQueryWindow ( hwnd , QW_PARENT , FALSE ) ;

     /* load our menus */
     hwndMenu = WinLoadMenu ( hwndFrame , (HMODULE) NULL , ID_RESOURCE ) ;

     /* open a device context and create a presentation space */
     hdc = WinOpenWindowDC ( hwnd ) ;
     hps = GpiCreatePS ( hab , hdc , & sizl ,
                         PU_ARBITRARY | GPIT_MICRO | GPIA_ASSOC ) ;

     /* determine screen dimensions */
     cxScreen = WinQuerySysValue (HWND_DESKTOP , SV_CXSCREEN ) ;
     cyScreen = WinQuerySysValue (HWND_DESKTOP , SV_CYSCREEN ) ;

     /* calculate an initial window position and size */
     xLeft = cxScreen / 8 ;
     yBottom = cyScreen / 2 ;
     cxWidth = cxScreen / 3 ;
     cyHeight = cyScreen / 2 ;

     /* get the device resolutions so we can make the face appear circular */
     DevQueryCaps ( hdc , CAPS_VERTICAL_RESOLUTION , 1L , & cyRes ) ;
     DevQueryCaps ( hdc , CAPS_HORIZONTAL_RESOLUTION , 1L , & cxRes ) ;

     /* position the window and make it visible */
     WinSetWindowPos ( hwndFrame , HWND_TOP ,
                       (SHORT) xLeft , (SHORT) yBottom ,
                       (SHORT) cxWidth , (SHORT) cyHeight ,
                       SWP_SIZE | SWP_MOVE | SWP_ACTIVATE ) ;

     /* have we been asked to start as an icon? */
     if ( fStartAsIcon )
         WinSetWindowPos ( hwndFrame , NULL , 0 , 0 , 0 , 0 , SWP_MINIMIZE ) ;

     WinShowWindow ( hwndFrame , TRUE ) ;

     /* get the time in a format for dislaying */
     DosGetDateTime ( & dt ) ;
     dt . hours = (UCHAR)(( dt . hours * 5 ) % 60 + dt . minutes / 12 );

     /* start a timer */
     WinStartTimer ( hab , hwnd , ID_RESOURCE , 1000 ) ;
 }


 /*
     ClkSize() -- Window Sizing Processing

     When the window has been sized, we calculate a page
     rectangle which:  (a) fills the window rectangle in
     either the x or y dimension, (b) appears square, and
     (c) is centered in the window rectangle.
 */
 VOID ClkSize ( HWND hwnd )
 {
     HWND hwndFrame ;
     RECTL rclWindow ;
     ULONG cxWidth , cyHeight ;
     ULONG cxSquare , cySquare , cxEdge , cyEdge ;

     /* get the width and height of the window rectangle */
     WinQueryWindowRect ( hwnd , & rclWindow ) ;
     cxWidth = rclWindow . xRight - rclWindow . xLeft ;
     cyHeight = rclWindow . yTop - rclWindow . yBottom ;

     /* assume the size of the page rectangle is constrained in the y dimensio
      * compute the x size which would make the rectangle appear square, then
      * check the assumption and do the reverse calculation if necessary */

     cySquare = cyHeight ;
     cxSquare = ( cyHeight * cxRes ) / cyRes ;
     if ( cxWidth < cxSquare ) {
         cxSquare = cxWidth ;
         cySquare = ( cxWidth * cyRes ) / cxRes ;
     }

     /* fill in the page rectangle and set the page viewport */
     cxEdge = ( cxWidth - cxSquare ) / 2 ;
     cyEdge = ( cyHeight - cySquare ) / 2 ;
     rclPage . xLeft = cxEdge ;
     rclPage . xRight = cxWidth - cxEdge ;
     rclPage . yBottom = cyEdge ;
     rclPage . yTop = cyHeight - cyEdge ;
     f = GpiSetPageViewport ( hps , & rclPage ) ;

     /* are we iconic? */
     hwndFrame = WinQueryWindow ( hwnd , QW_PARENT , FALSE ) ;
     f = WinQueryWindowPos ( hwndFrame , & swp ) ;
     fIconic = swp . fs & SWP_MINIMIZE ;
     fShowSecondHand = ! fIconic ;
 }


 /*
     ClkTimer() -- Handles timer events
 */
 VOID ClkTimer ( VOID )
 {
     DATETIME dtNew ;

     /* get the new time */
     DosGetDateTime ( & dtNew ) ;

     /* adjust the hour hand */
     dtNew . hours =(UCHAR)( ( dtNew . hours * 5 ) % 60 + dtNew . minutes / 12

     /* if we must move the hour and minute hands, redraw it all */
     if ( dtNew . minutes != dt . minutes ) {

         ClkDrawFace ( hps ) ;
         ClkDrawHand ( hps , HT_HOUR , dtNew . hours ) ;
         ClkDrawHand ( hps , HT_MINUTE , dtNew . minutes ) ;

         if ( fShowSecondHand ) {
             GpiSetMix ( hps , FM_INVERT ) ;
             ClkDrawHand ( hps , HT_SECOND , dtNew . seconds ) ;
         }
     }

     /* otherwise just undraw the old second hand and draw the new */
     else if ( fShowSecondHand ) {
         GpiSetMix ( hps , FM_INVERT ) ;
         ClkDrawHand ( hps , HT_SECOND , dt . seconds ) ;
         ClkDrawHand ( hps , HT_SECOND , dtNew . seconds ) ;
     }

     dt = dtNew ;
 }


 /*
     ClkCommand() -- Handle WM_COMMAND events
 */
 VOID ClkCommand ( HWND hwnd , MPARAM mp1 )
 {
     switch ( SHORT1FROMMP ( mp1 ) ) {

         case IDM_ABOUT :
             WinDlgBox ( HWND_DESKTOP , hwnd , ClkAboutDlgProc , (HMODULE) NUL
                         IDD_ABOUT , NULL ) ;
             break ;

         case IDM_TICKS :
             WinDlgBox ( HWND_DESKTOP , hwnd , ClkTicksDlgProc , (HMODULE) NUL
                         IDD_TICKS , NULL ) ;
             break ;

         case IDM_COLORS :
             WinDlgBox ( HWND_DESKTOP , hwnd , ClkColorsDlgProc , (HMODULE) NU
                         IDD_COLORS , NULL ) ;
             break ;

         case IDM_HIDECONTROLS :
             ClkHideFrameControls ( hwndFrame ) ;
             break ;
     }
 }


 /*
     ClkHideFrameControls() -- Hide the title bar and associated controls
 */
 VOID ClkHideFrameControls ( HWND hwndFrame )
 {

     hwndTitleBar = WinWindowFromID ( hwndFrame , FID_TITLEBAR ) ;
     hwndSysMenu = WinWindowFromID ( hwndFrame , FID_SYSMENU ) ;
     hwndMinMax = WinWindowFromID ( hwndFrame , FID_MINMAX ) ;
     hwndMenu = WinWindowFromID ( hwndFrame , FID_MENU ) ;

     WinSetParent ( hwndTitleBar , HWND_OBJECT , FALSE ) ;
     WinSetParent ( hwndSysMenu , HWND_OBJECT , FALSE ) ;
     WinSetParent ( hwndMinMax , HWND_OBJECT , FALSE ) ;
     WinSetParent ( hwndMenu , HWND_OBJECT , FALSE ) ;

     WinSendMsg ( hwndFrame , WM_UPDATEFRAME ,
         ( MPARAM ) ( FCF_TITLEBAR | FCF_SYSMENU | FCF_MINMAX | FCF_MENU ) ,
         NULL ) ;

     fControlsHidden = TRUE ;
 }


 /*
     ClkShowFrameControls() -- Show the title bar and associated controls
 */
 VOID ClkShowFrameControls ( HWND hwndFrame )
 {

     WinSetParent ( hwndTitleBar , hwndFrame , FALSE ) ;
     WinSetParent ( hwndSysMenu , hwndFrame , FALSE ) ;
     WinSetParent ( hwndMinMax , hwndFrame , FALSE ) ;
     WinSetParent ( hwndMenu , hwndFrame , FALSE ) ;

     WinSendMsg ( hwndFrame , WM_UPDATEFRAME ,
         ( MPARAM ) ( FCF_TITLEBAR | FCF_SYSMENU | FCF_MINMAX | FCF_MENU ) ,
         NULL ) ;
     WinInvalidateRect ( hwndFrame , NULL , TRUE ) ;

     fControlsHidden = FALSE ;
 }


 WNDPROC.C
 CD-ROM Disc Path:   \SAMPCODE\OS2SDK\OS2SDK12\BIO\WNDPROC.C

 /* wndproc.c -- Window handling routines
    Created by Microsoft Corporation, 1989
 */
 #define INCL_GPI
 #define INCL_WIN
 #include <os2.h>

 #include "bio.h"
 #include <time.h>
 #include <stdio.h>

 /* Read-only global variables */
 extern HAB      hAB;
 extern HWND     hwndApp;
 extern HWND     hwndAppFrame, hwndKidFrame;
 extern char     szAppName[];
 extern PFNWP OldFrameWndProc;

 /* Write-once Global variables */
 LONG Color[] = {CLR_RED, CLR_GREEN, CLR_BLUE};
 FONTMETRICS     tmFontInfo;
 SHORT                cxLegendField;
 SHORT                cxDateField;
 /* parameter used when creating a device context for a memory device        *
 PSZ         dcdatablk[9] = {(PSZ)0
                            ,(PSZ)"DISPLAY"
                            ,(PSZ)0
                            ,(PSZ)0
                            ,(PSZ)0
                            ,(PSZ)0
                            ,(PSZ)0
                            ,(PSZ)0
                            ,(PSZ)0
                            };

 /* Read-Write global variables */
 double          Born;
 long            Day, SelectDay;
 BOOL            bKid = TRUE;
 BOOL            bBorn = TRUE;
 RECTL           rclClient;
 int             LinesPerPage;
 void                BioGetDate(HWND);

 /*  BioWndProc() - Parent WndProc message processing routine.
 *
 *   Purpose:
 *       WndProc callback function to handle all messages for parent window.
 *
 *   Arguments:
 *       hWnd          - Handle of Window owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 *
 *   Globals (modified):
 *       Born          - Birthdate in julian days.  Read from WIN.INI.
 *        SelectDay     - Current day being tracked, day is highlighted.
 *                        Value is days from birthdate.
 *                        Initialized to current day in WM_CREATE processing.
 *       daylight      - Defined by environment string TZ.  If no such string,
 *       timezone        default TZ=PST8PDT used.  If daylight field is
 *                       used, daylight time correction will occur.  See
 *                       documentation of tzset() C run-time function.
 *       Day           - Day number from date born which is top line being
 *                       displayed.  Initially three days before SelectDay.
 *       bKid          - Boolean indicating whether legend is visible.
 *       bBorn         - Boolean indicating whether valid birtdate entered or
 *                       defined in WIN.INI.  Nothing graphed until valid.
 *        rclClient     - Size of client area defined by WM_SIZE message
 *       LinesPerPage  - Number of system font lines on client area, defined
 *                       by WM_SIZE message handling
 *       Color[]       - Set of colored pens used to identify cycles.
 *       tmFontInfo    - Text Metric structure defined during WM_CREATE
 *
 *   Globals (referenced):
 *       hAB           - Handle to the Anchor Block
 *       hwndAppFrame  - Window handle of parent window's frame
 *       hwndKidFrame  - Handle to child window used for showing/moving legend
 *        szAppName[]   - RC file program name (Biorhythm).
 *
 *   Description:
 *       Handle all messages for the parent window.
 *
 *   Limits:
 *       N/A
 *
 */


 MRESULT CALLBACK BioWndProc( hWnd, message, mp1, mp2 )
 HWND   hWnd;
 USHORT message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     int         iDay, i;
     HPS         hPS;
     HPS         hMemPS;
     RECTL       rc;
     POINTL        ptlTextBox[5];
     SIZEL        pgsi;
     HDC                hdcMem;
     HBITMAP        hbm;
     BITMAPINFOHEADER bmih;
     POINTL        ptlPoints[3];

     switch( message )
     {

     case WM_CREATE:
         BioGetDate(hWnd);

         /* Put date of the day three lines down on display */
         Day = SelectDay - 3;

         /* Initially set elevator */
         iDay = (int)(Day/365);
         WinSendMsg( WinWindowFromID( WinQueryWindow(hWnd,QW_PARENT,FALSE),
                                      FID_VERTSCROLL),
                     SBM_SETPOS, (MPARAM)MAKEULONG(iDay, 0), 0L );

         /* Get System font text metrics */
         hPS = WinGetPS( hWnd );
         GpiQueryFontMetrics( hPS, (LONG)sizeof tmFontInfo, &tmFontInfo );
         /* Get sizes of long strings to be used as yard sticks for sizing
            windows and objects.  This is necessary because of new system
            proportional fonts. */
         GpiQueryTextBox( hPS, 10L, "Emotional ", TXTBOX_COUNT, ptlTextBox );
         cxLegendField = (SHORT)ptlTextBox[TXTBOX_CONCAT].x;
         GpiQueryTextBox( hPS, 10L, "W 99-99-99", TXTBOX_COUNT, ptlTextBox );
         cxDateField = (SHORT)ptlTextBox[TXTBOX_CONCAT].x;
         WinReleasePS( hPS );
         break;

     case WM_CLOSE:
         WinPostMsg( hWnd, WM_QUIT, 0L, 0L );
         break;

     case WM_COMMAND:
         switch (LOUSHORT(mp1)) {
             case IDM_DATES:
                 if (WinDlgBox( HWND_DESKTOP, hWnd, (PFNWP)BioDlg, (HMODULE) N
                    WinInvalidateRect( hWnd, NULL, FALSE );
                    iDay = (int)(Day/365);
                    WinSendMsg( WinWindowFromID( hwndAppFrame, FID_VERTSCROLL)
                                SBM_SETPOS, (MPARAM)MAKEULONG(iDay, 0), 0L );
                 }
                 break;

             case IDM_LEGEND:
                 if (bKid = !bKid) {
                    WinSendMsg( WinWindowFromID( hwndAppFrame, FID_MENU),
                                MM_SETITEMATTR,
                                (MPARAM)MAKEULONG( IDM_LEGEND, TRUE ),
                                (MPARAM)MAKEULONG( MIA_CHECKED, MIA_CHECKED) )
                    WinShowWindow( hwndKidFrame, TRUE );
                 } else {
                    WinSendMsg( WinWindowFromID( hwndAppFrame, FID_MENU),
                                MM_SETITEMATTR,
                                (MPARAM)MAKEULONG( IDM_LEGEND, TRUE ),
                                (MPARAM)MAKEULONG( MIA_CHECKED, 0) );
                    WinShowWindow( hwndKidFrame, FALSE );
                 }
                 break;

             case IDM_COPY:
                 /* Get access to clipboard. */
                 WinOpenClipbrd( hAB );
                 /* Wipe the slate clean. */
                 WinEmptyClipbrd( hAB );

                 /* Bitmap header for bitmap the size of the window. */
                 pgsi.cx = rclClient.xRight;
                 pgsi.cy = rclClient.yTop;
                 bmih.cbFix = 12;
                 bmih.cx = (USHORT)pgsi.cx;
                 bmih.cy = (USHORT)pgsi.cy;
                 bmih.cPlanes = 1;
                 bmih.cBitCount = 24;

                 /* Get a memory dc. */
                 hdcMem = DevOpenDC( hAB
                               , OD_MEMORY
                               , (PSZ)"*"
                               , 8L
                               , (PDEVOPENDATA)dcdatablk
                               , (HDC)NULL
                               );

                 /* Get a memory PS that will be used to manipulate image */
                 hMemPS = GpiCreatePS( hAB
                                 , hdcMem
                                 , (PSIZEL)&pgsi
                                 , (LONG)PU_PELS | GPIT_MICRO | GPIA_ASSOC
                                 );

                 /* Create a bitmap to hold image */
                 hbm = GpiCreateBitmap(hMemPS, &bmih, 0L, (PBYTE)NULL, (PBITMA
                 /* Select bitmap into PS */
                 GpiSetBitmap( hMemPS, hbm );
                 /* BitBlt window client area into memory bitmap */
                 ptlPoints[0].x = 0;
                 ptlPoints[0].y = 0;
                 ptlPoints[1].x = pgsi.cx;
                 ptlPoints[1].y = pgsi.cy;
                 ptlPoints[2].x = 0;
                 ptlPoints[2].y = 0;
                 hPS = WinGetPS( hWnd );
                 GpiBitBlt(hMemPS, hPS, 3L, ptlPoints, ROP_SRCCOPY, BBO_OR);
                 WinReleasePS( hPS );

                 /* Put bitmap into the clipboard. */
                 WinSetClipbrdData( hAB, (ULONG)hbm, CF_BITMAP, CFI_HANDLE );

                 /* Tidy up */
                 WinCloseClipbrd( hAB );
                 GpiSetBitmap( hMemPS, NULL );
                 GpiDestroyPS( hMemPS );
                 DevCloseDC( hdcMem );
                 break;

             case IDM_ABOUT:
                 WinDlgBox( HWND_DESKTOP, hWnd, (PFNWP)About, (HMODULE) NULL,
                            IDD_ABOUT, NULL );
                 break;

             default:
                 break;
             }
             break;

     case WM_SIZE:
         WinQueryWindowRect( hWnd, &rclClient );
         LinesPerPage = (int)(rclClient.yTop / tmFontInfo.lMaxBaselineExt);
         WinSetWindowPos( hwndKidFrame, NULL, 10, 10, 0, 0, SWP_MOVE );
         break;

     case WM_CHAR:
         /* Convert keyboard to scroll bar messages to support scrolling,
            paging, etc. with keyboard interface. */
         if ( (ULONG)mp1 & KC_KEYUP )
             return WinDefWindowProc( hWnd, message, mp1, mp2 );
         switch (HIUSHORT( mp2 )) {
             case VK_UP:
                 mp2 = (MPARAM)MAKEULONG( 0, SB_LINEUP );
                 break;
             case VK_DOWN:
                 mp2 = (MPARAM)MAKEULONG( 0, SB_LINEDOWN );
                 break;
             case VK_PAGEUP:
                 mp2 = (MPARAM)MAKEULONG( 0, SB_PAGEUP );
                 break;
             case VK_PAGEDOWN:
                 mp2 = (MPARAM)MAKEULONG( 0, SB_PAGEDOWN );
                 break;
             default:
                 return WinDefWindowProc( hWnd, message, mp1, mp2 );
                 break;
         }
         return WinSendMsg( hWnd, WM_VSCROLL, mp1, mp2 );
         break;

     case WM_VSCROLL:
         /* Don't allow any processing until valid birth date entered */
         if (!bBorn) break;

         /* Setup for scroll window - full width of client area is scrolled */
         WinCopyRect( hAB, &rc, &rclClient );
         switch (HIUSHORT(mp2)) {
           case SB_LINEUP:
             /* Update top day of display */
             Day--;
             rc.yTop = rclClient.yTop - tmFontInfo.lMaxBaselineExt;
             rc.yBottom = rclClient.yTop - (LinesPerPage-1) * tmFontInfo.lMaxB
             WinScrollWindow( hWnd, 0, (SHORT)-tmFontInfo.lMaxBaselineExt, &rc
                              NULL, NULL, NULL, SW_INVALIDATERGN );
             break;
           case SB_LINEDOWN:
             /* Update top day of display */
             Day++;
             rc.yTop = rclClient.yTop - 2*tmFontInfo.lMaxBaselineExt;
             rc.yBottom = rclClient.yTop - (LinesPerPage) * tmFontInfo.lMaxBas
             WinScrollWindow( hWnd, 0, (SHORT)tmFontInfo.lMaxBaselineExt, &rc,
                              NULL, NULL, NULL, SW_INVALIDATERGN );
             break;
           case SB_PAGEUP:
             Day -= (LinesPerPage-1);
             break;
           case SB_PAGEDOWN:
             Day += (LinesPerPage-1);
             break;
           case SB_SLIDERPOSITION:
             /* Set to birthday of each year because 100 year scale maps to
                default 100 position scroll bar */
             Day = (long)(LOUSHORT(mp2) * 365.25);
             break;
         default:
             return 0L;
       }
       /* Update scroll bar elevator */
       iDay = (int)(Day/365);
       WinSendMsg( WinWindowFromID( hwndAppFrame, FID_VERTSCROLL),
                   SBM_SETPOS, (MPARAM)MAKEULONG(iDay, 0), 0L );
       /* All but LINEUP/DOWN need full repaint of client area */
       if ((HIUSHORT(mp2) != SB_LINEUP) && (HIUSHORT(mp2) != SB_LINEDOWN ))
          WinInvalidateRect( hWnd, NULL, FALSE );
       WinUpdateWindow( hWnd );
       break;

     case WM_PAINT:
         APPPaint( hWnd );
         break;

     case WM_BUTTON1DOWN:
         /* Don't allow any processing until valid birthdate entered */
         if (!bBorn) break;

         /* Unhighlight previously selected line and highlight new line */
         WinCopyRect( hAB, &rc, &rclClient );
         hPS = WinGetPS( hWnd );
         for(i=0; i<2; i++) {
           /* Make sure line is visible before (un)highlighting */
           if ((SelectDay >= Day) && (SelectDay - Day < LinesPerPage-1)) {
              rc.yTop = rclClient.yTop - (int)(SelectDay - Day + 1) * tmFontIn
              rc.yBottom = rc.yTop - tmFontInfo.lMaxBaselineExt + 1;
              WinInvertRect( hPS, &rc );
           }
           /* New line to highlight */
           SelectDay = Day + (rclClient.yTop - HIUSHORT(mp1)) /
                       tmFontInfo.lMaxBaselineExt - 1;
         }
         WinReleasePS( hPS );
         break;

     /* Draw highlight on selected day */
     if ((SelectDay >= Day) && (SelectDay - Day < LinesPerPage - 1)) {
         rc.xRight = rclClient.xRight;
         rc.xLeft = rclClient.xLeft;
     }
     default:
         return WinDefWindowProc( hWnd, message, mp1, mp2 );
         break;
     }
     return( 0L );
 }

 /*  KidWndProc() - Child WndProc handling legend display.
 *
 *   Purpose:
 *       WndProc callback function to handle all messages for legend child.
 *
 *   Arguments:
 *       hWnd          - Handle of Window owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 *
 *   Globals (referenced):
 *       hwndApp       - Window handle of parent window's client area
 *       tmFontInfo    - Text Metric structure defined during WM_CREATE
 *       Color[]       - Set of colored pens used to identify cycles.
 *
 *   Description:
 *       Display legend information relating graph line styles to each
 *       cyle: physical, emotional and intellectual.  Notifies parent
 *       to hide child if child window is instructed to close by user.
 */

 /* Read-only global variables */
 extern HWND     hwndApp;

 MRESULT CALLBACK KidWndProc( hWnd, message, mp1, mp2 )
 HWND    hWnd;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     HPS         hPS;
     RECTL       rc;
     POINTL      ptl;
     int         i;

     switch( message )
     {
     case WM_CHAR:
         /* Convert keyboard to scroll bar messages to support scrolling,
            paging, etc. with keyboard interface. */
         WinSendMsg( hwndApp, message, mp1, mp2 );
         break;

         case WM_PAINT:
             hPS = WinBeginPaint( hWnd, NULL, NULL );

             /* Erase client area */
             WinQueryWindowRect( hWnd, &rc );
             WinFillRect( hPS, &rc, CLR_PALEGRAY );

             ptl.x = 0;
             ptl.y = tmFontInfo.lMaxDescender;
             GpiCharStringAt( hPS, &ptl, 8L, (PCH)"Physical" );
             ptl.y += tmFontInfo.lMaxBaselineExt;
             GpiCharStringAt( hPS, &ptl, 9L, (PCH)"Emotional" );
             ptl.y += tmFontInfo.lMaxBaselineExt;
             GpiCharStringAt( hPS, &ptl, 9L, (PCH)"Intellect" );

             for (i=0; i<3; i++ ) {
                 GpiSetColor( hPS, Color[i] );
                 ptl.x = cxLegendField;
                 ptl.y = i * tmFontInfo.lMaxBaselineExt +
                         tmFontInfo.lMaxBaselineExt/2;
                 GpiMove( hPS, &ptl );
                 ptl.x = rc.xRight - tmFontInfo.lAveCharWidth;
                 GpiLine( hPS, &ptl );
             }

             WinEndPaint( hPS );
             break;

         case WM_BUTTON1UP:
             /* Quick way to make Legend window disappear using mouse. */
             WinPostMsg( hwndApp, WM_COMMAND, (MPARAM)MAKEULONG(IDM_LEGEND, 0)
             break;

         case WM_TRANSLATEACCEL:
             /* Change window handle.  Child window's frame will block
                ALT handling, so bypass frame.  Return window handle
                of main window's client area.  Now the ALT key message
                handling will be passed on up the chain of windows to
                the main window's frame for proper ALT key message
                handling.  The main window's menu bar will highlight
                even if the child window has the focus. */
             return WinDefWindowProc( hwndApp, message, mp1, mp2 );
             break;

         default:
             return WinDefWindowProc( hWnd, message, mp1, mp2 );
             break;
     }
     return( 0L );
 }
 /*
 */

 /*  FrameWndProc() - Subclass routine for frame.
 *
 *   Purpose:
 *        Handle WM_QUERYTRACKINFO message so that a minimum horizontal and
 *        vertical window size can be controlled.  This minimum size keeps
 *        the tabulated data from overlapping and leaves at least 4 rows
 *        of data visible.
 *
 *   Arguments:
 *       hWnd          - Handle of Window owning message
 *       message       - Message itself
 *       mp1           - Extra message-dependent info
 *       mp2           - Extra message-dependent info
 *
 *   Globals (referenced):
 *        OldFrameWndProc - Original Frame Window procedure.
 *
 *   Limits:
 *       N/A
 *
 */

 MRESULT CALLBACK FrameWndProc( hWnd, message, mp1, mp2 )
 HWND    hWnd;
 USHORT  message;
 MPARAM  mp1;
 MPARAM  mp2;
 {
     switch( message )
     {
         case WM_QUERYTRACKINFO:
             (*OldFrameWndProc)( hWnd, message, mp1, mp2 );
             /* Limit vertical and horizontal minimum size.  Must take into
                account menu, title, border and font widths and heights for
                device independence. */
             ((PTRACKINFO)mp2)->ptlMinTrackSize.x = cxDateField * 2 + cxDateFi
             ((PTRACKINFO)mp2)->ptlMinTrackSize.y =
                 tmFontInfo.lMaxBaselineExt * 5 +
                 WinQuerySysValue( HWND_DESKTOP, SV_CYMENU ) +
                 WinQuerySysValue( HWND_DESKTOP, SV_CYTITLEBAR ) +
                 WinQuerySysValue( HWND_DESKTOP, SV_CYSIZEBORDER ) * 2 +
                 WinQuerySysValue( HWND_DESKTOP, SV_CYBORDER ) * 2;
             return (MRESULT) TRUE;
             break;

         default:
             return (*OldFrameWndProc)( hWnd, message, mp1, mp2 );
             break;
     }
     return( 0L );
 }

 void BioGetDate(HWND hWnd) {
     int         year, month;
     double      day;

     /* Read in birth date from OS2.INI.  Error value is 12-31-1899,
        which is out of range for valid entries. */
     year = WinQueryProfileInt( hAB, szAppName, "Year", 1899 );
     month = WinQueryProfileInt( hAB, szAppName, "Month", 12 );
     day = (double)WinQueryProfileInt( hAB, szAppName, "Day", 31 );

     /* Compute date of birth in julian days */
     Born = julian( year, month, day );

     /* Get time zone environment information */
     tzset();
     /*
             System clock starts 1-1-1970.  Get julian date then and how many
             days have elapsed since, so that number of days since birth date
             can be determined
     */
     SelectDay  = (long)(julian( 1970, 1, 1.0 ) +
        (double)((time(NULL) - timezone + (long)daylight*3600)/86400) -
        Born );

     /* If no valid OS2.INI info then automatically bring up dialog box */
     if (year < 1900) {
        bBorn = FALSE;
        WinPostMsg( hWnd, WM_COMMAND, (MPARAM)MAKEULONG(IDM_DATES, 0), 0L );
     }
 }
 Advanced OS/2 Sample Code


 ARGC.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\ARGC.ASM

         title        ARGC -- Return Argument Count
         page    55,132
         .286

 ;
 ; ARGC.ASM
 ;
 ; Return count of command line arguments.  Treats blanks and
 ; tabs as whitespace.
 ;
 ; Assemble with:  C> masm argc.asm;
 ;
 ; Call with:    N/A
 ;
 ; Returns:        AX    = argument count (always >= 1)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argc            ; make ARGC available to Linker

                                 ; local variables
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argc    proc    near

         enter   4,0             ; make room for local variables

         push        es                ; save original ES, BX, and CX
         push    bx
         push    cx

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; check operation status
         mov     ax,1            ; force argc >= 1
         jnz     argc3           ; inexplicable failure

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

 argc0:  inc     bx              ; ignore useless first field
         cmp     byte ptr es:[bx],0
         jne     argc0

 argc1:  mov     cx,-1           ; set flag = outside argument

 argc2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argc3           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argc1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argc1           ; outside argument if ASCII tab

                                 ; otherwise not blank or tab,
         jcxz    argc2           ; jump if already inside argument

         inc     ax              ; else found argument, count it
         not     cx              ; set flag = inside argument
         jmp     argc2           ; and look at next character

 argc3:  pop     cx              ; restore original BX, CX, ES
         pop     bx
         pop     es
         leave                   ; discard local variables
         ret                     ; return AX = argument count

 argc    endp

 _TEXT   ends

         end


 ARGC.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\ARGC.ASM

         title        ARGC -- Return Argument Count
         page    55,132
         .286

 ;
 ; ARGC.ASM
 ;
 ; Return count of command line arguments.  Treats blanks and
 ; tabs as whitespace.
 ;
 ; Assemble with:  C> masm argc.asm;
 ;
 ; Call with:    N/A
 ;
 ; Returns:        AX    = argument count (always >= 1)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argc            ; make ARGC available to Linker

                                 ; local variables
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argc    proc    near

         enter   4,0             ; make room for local variables

         push        es                ; save original ES, BX, and CX
         push    bx
         push    cx

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; check operation status
         mov     ax,1            ; force argc >= 1
         jnz     argc3           ; inexplicable failure

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

 argc0:  inc     bx              ; ignore useless first field
         cmp     byte ptr es:[bx],0
         jne     argc0

 argc1:  mov     cx,-1           ; set flag = outside argument

 argc2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argc3           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argc1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argc1           ; outside argument if ASCII tab

                                 ; otherwise not blank or tab,
         jcxz    argc2           ; jump if already inside argument

         inc     ax              ; else found argument, count it
         not     cx              ; set flag = inside argument
         jmp     argc2           ; and look at next character

 argc3:  pop     cx              ; restore original BX, CX, ES
         pop     bx
         pop     es
         leave                   ; discard local variables
         ret                     ; return AX = argument count

 argc    endp

 _TEXT   ends

         end


 ARGC.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH08\ARGC.ASM

         title        ARGC -- Return Argument Count
         page    55,132
         .286

 ;
 ; ARGC.ASM
 ;
 ; Return count of command line arguments.  Treats blanks and
 ; tabs as whitespace.
 ;
 ; Assemble with:  C> masm argc.asm;
 ;
 ; Call with:    N/A
 ;
 ; Returns:        AX    = argument count (always >= 1)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argc            ; make ARGC available to Linker

                                 ; local variables
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argc    proc    near

         enter   4,0             ; make room for local variables

         push        es                ; save original ES, BX, and CX
         push    bx
         push    cx

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; check operation status
         mov     ax,1            ; force argc >= 1
         jnz     argc3           ; inexplicable failure

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

 argc0:  inc     bx              ; ignore useless first field
         cmp     byte ptr es:[bx],0
         jne     argc0

 argc1:  mov     cx,-1           ; set flag = outside argument

 argc2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argc3           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argc1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argc1           ; outside argument if ASCII tab

                                 ; otherwise not blank or tab,
         jcxz    argc2           ; jump if already inside argument

         inc     ax              ; else found argument, count it
         not     cx              ; set flag = inside argument
         jmp     argc2           ; and look at next character

 argc3:  pop     cx              ; restore original BX, CX, ES
         pop     bx
         pop     es
         leave                   ; discard local variables
         ret                     ; return AX = argument count

 argc    endp

 _TEXT   ends

         end


 ARGV.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH08\ARGV.ASM

         title        ARGV -- Return Argument Pointer
         page    55,132
         .286

 ;
 ; ARGV.ASM
 ;
 ; Return address and length of specified command line argument
 ; or fully qualified program name.  Treats blanks and tabs as
 ; whitespace.
 ;
 ; Assemble with:  C> masm argv.asm;
 ;
 ; Call with:    AX    = argument number (0 based)
 ;
 ; Returns:      ES:BX = argument address
 ;                AX    = argument length (0 = no argument)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Note:  If called with AX=0 (argv[0]), returns ES:BX
 ; pointing to fully qualified program name in environment
 ; block and AX=length.
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argv            ; make ARGV available to Linker

                                 ; local variables...
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argv    proc    near

         enter   4,0             ; make room for local variables
         push    cx              ; save original CX and DI
         push    di

         push    ax              ; save argument number

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; test operation status
         pop     ax              ; restore argument number
         jnz     argv7           ; jump if DosGetEnv failed

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

         or        ax,ax                ; is requested argument = 0?
         jz      argv8           ; yes, jump to get program name

 argv0:  inc     bx              ; scan off first field
         cmp     byte ptr es:[bx],0
         jne     argv0

         xor     ah,ah           ; initialize argument counter

 argv1:  mov     cx,-1           ; set flag = outside argument

 argv2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv7           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argv1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argv1           ; outside argument if ASCII tab

                                 ; if not blank or tab...
         jcxz    argv2           ; jump if already inside argument

         inc     ah              ; else count arguments found
         cmp     ah,al           ; is this the one we're looking for?
         je      argv4           ; yes, go find its length
         not     cx              ; no, set flag = inside argument
         jmp     argv2           ; and look at next character

 argv4:                          ; found desired argument, now
                                 ; determine its length...
         mov        ax,bx                ; save parameterr starting address

 argv5:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv6           ; found end if null byte
         cmp     byte ptr es:[bx],blank
         je      argv6           ; found end if ASCII blank
         cmp     byte ptr es:[bx],tab
         jne     argv5           ; found end if ASCII tab

 argv6:  xchg    bx,ax           ; set ES:BX = argument address
         sub     ax,bx           ; and AX = argument length
         jmp     argvx           ; return to caller

 argv7:  xor     ax,ax           ; set AX = 0, argument not found
         jmp     argvx           ; return to caller

 argv8:                          ; special handling for argv=0
         xor     di,di           ; find the program name by
         xor     al,al           ; first skipping over all the
         mov     cx,-1           ; environment variables...
         cld
 argv9:  repne scasb             ; scan for double null (can't use
         scasb                        ; SCASW since might be odd address)
         jne     argv9           ; loop if it was a single null
         mov     bx,di           ; save program name address
         mov     cx,-1           ; now find its length...
         repne scasb             ; scan for another null byte
         not     cx              ; convert CX to length
         dec     cx
         mov     ax,cx           ; return length in AX

 argvx:                          ; common exit point
         pop     di              ; restore original CX and DI
         pop     cx
         leave                   ; discard stack frame
         ret                     ; return to caller

 argv    endp

 _TEXT   ends

         end


 ARGV.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\ARGV.ASM

         title        ARGV -- Return Argument Pointer
         page    55,132
         .286

 ;
 ; ARGV.ASM
 ;
 ; Return address and length of specified command line argument
 ; or fully qualified program name.  Treats blanks and tabs as
 ; whitespace.
 ;
 ; Assemble with:  C> masm argv.asm;
 ;
 ; Call with:    AX    = argument number (0 based)
 ;
 ; Returns:      ES:BX = argument address
 ;                AX    = argument length (0 = no argument)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Note:  If called with AX=0 (argv[0]), returns ES:BX
 ; pointing to fully qualified program name in environment
 ; block and AX=length.
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argv            ; make ARGV available to Linker

                                 ; local variables...
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argv    proc    near

         enter   4,0             ; make room for local variables
         push    cx              ; save original CX and DI
         push    di

         push    ax              ; save argument number

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; test operation status
         pop     ax              ; restore argument number
         jnz     argv7           ; jump if DosGetEnv failed

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

         or        ax,ax                ; is requested argument = 0?
         jz      argv8           ; yes, jump to get program name

 argv0:  inc     bx              ; scan off first field
         cmp     byte ptr es:[bx],0
         jne     argv0

         xor     ah,ah           ; initialize argument counter

 argv1:  mov     cx,-1           ; set flag = outside argument

 argv2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv7           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argv1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argv1           ; outside argument if ASCII tab

                                 ; if not blank or tab...
         jcxz    argv2           ; jump if already inside argument

         inc     ah              ; else count arguments found
         cmp     ah,al           ; is this the one we're looking for?
         je      argv4           ; yes, go find its length
         not     cx              ; no, set flag = inside argument
         jmp     argv2           ; and look at next character

 argv4:                          ; found desired argument, now
                                 ; determine its length...
         mov        ax,bx                ; save parameterr starting address

 argv5:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv6           ; found end if null byte
         cmp     byte ptr es:[bx],blank
         je      argv6           ; found end if ASCII blank
         cmp     byte ptr es:[bx],tab
         jne     argv5           ; found end if ASCII tab

 argv6:  xchg    bx,ax           ; set ES:BX = argument address
         sub     ax,bx           ; and AX = argument length
         jmp     argvx           ; return to caller

 argv7:  xor     ax,ax           ; set AX = 0, argument not found
         jmp     argvx           ; return to caller

 argv8:                          ; special handling for argv=0
         xor     di,di           ; find the program name by
         xor     al,al           ; first skipping over all the
         mov     cx,-1           ; environment variables...
         cld
 argv9:  repne scasb             ; scan for double null (can't use
         scasb                        ; SCASW since might be odd address)
         jne     argv9           ; loop if it was a single null
         mov     bx,di           ; save program name address
         mov     cx,-1           ; now find its length...
         repne scasb             ; scan for another null byte
         not     cx              ; convert CX to length
         dec     cx
         mov     ax,cx           ; return length in AX

 argvx:                          ; common exit point
         pop     di              ; restore original CX and DI
         pop     cx
         leave                   ; discard stack frame
         ret                     ; return to caller

 argv    endp

 _TEXT   ends

         end


 ARGV.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\ARGV.ASM

         title        ARGV -- Return Argument Pointer
         page    55,132
         .286

 ;
 ; ARGV.ASM
 ;
 ; Return address and length of specified command line argument
 ; or fully qualified program name.  Treats blanks and tabs as
 ; whitespace.
 ;
 ; Assemble with:  C> masm argv.asm;
 ;
 ; Call with:    AX    = argument number (0 based)
 ;
 ; Returns:      ES:BX = argument address
 ;                AX    = argument length (0 = no argument)
 ;
 ; Uses:         nothing (other registers preserved)
 ;
 ; Note:  If called with AX=0 (argv[0]), returns ES:BX
 ; pointing to fully qualified program name in environment
 ; block and AX=length.
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT

         public  argv            ; make ARGV available to Linker

                                 ; local variables...
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

 argv    proc    near

         enter   4,0             ; make room for local variables
         push    cx              ; save original CX and DI
         push    di

         push    ax              ; save argument number

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; test operation status
         pop     ax              ; restore argument number
         jnz     argv7           ; jump if DosGetEnv failed

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

         or        ax,ax                ; is requested argument = 0?
         jz      argv8           ; yes, jump to get program name

 argv0:  inc     bx              ; scan off first field
         cmp     byte ptr es:[bx],0
         jne     argv0

         xor     ah,ah           ; initialize argument counter

 argv1:  mov     cx,-1           ; set flag = outside argument

 argv2:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv7           ; exit if null byte
         cmp     byte ptr es:[bx],blank
         je      argv1           ; outside argument if ASCII blank
         cmp     byte ptr es:[bx],tab
         je      argv1           ; outside argument if ASCII tab

                                 ; if not blank or tab...
         jcxz    argv2           ; jump if already inside argument

         inc     ah              ; else count arguments found
         cmp     ah,al           ; is this the one we're looking for?
         je      argv4           ; yes, go find its length
         not     cx              ; no, set flag = inside argument
         jmp     argv2           ; and look at next character

 argv4:                          ; found desired argument, now
                                 ; determine its length...
         mov        ax,bx                ; save parameterr starting address

 argv5:  inc     bx              ; point to next character
         cmp     byte ptr es:[bx],0
         je      argv6           ; found end if null byte
         cmp     byte ptr es:[bx],blank
         je      argv6           ; found end if ASCII blank
         cmp     byte ptr es:[bx],tab
         jne     argv5           ; found end if ASCII tab

 argv6:  xchg    bx,ax           ; set ES:BX = argument address
         sub     ax,bx           ; and AX = argument length
         jmp     argvx           ; return to caller

 argv7:  xor     ax,ax           ; set AX = 0, argument not found
         jmp     argvx           ; return to caller

 argv8:                          ; special handling for argv=0
         xor     di,di           ; find the program name by
         xor     al,al           ; first skipping over all the
         mov     cx,-1           ; environment variables...
         cld
 argv9:  repne scasb             ; scan for double null (can't use
         scasb                        ; SCASW since might be odd address)
         jne     argv9           ; loop if it was a single null
         mov     bx,di           ; save program name address
         mov     cx,-1           ; now find its length...
         repne scasb             ; scan for another null byte
         not     cx              ; convert CX to length
         dec     cx
         mov     ax,cx           ; return length in AX

 argvx:                          ; common exit point
         pop     di              ; restore original CX and DI
         pop     cx
         leave                   ; discard stack frame
         ret                     ; return to caller

 argv    endp

 _TEXT   ends

         end


 ASMHELP.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH19\ASMHELP.ASM

         title        ASMHELP -- Sample MASM DLL
         page    55,132
         .286

 ;
 ; ASMHELP.ASM
 ;
 ; Source code for the MASM dynlink library ASMHELP.DLL.
 ;
 ; Assemble with:  C> masm asmhelp.asm;
 ; Link with:  C> link asmhelp,asmhelp.dll,,os2,asmhelp
 ;
 ; Exports two routines for use by MASM programs:
 ;
 ; ARGC  Returns count of command line arguments.
 ;       Treats blanks and tabs as whitespace.
 ;
 ;       Calling sequence:
 ;
 ;       push    seg argcnt      ; receives argument count
 ;       push    offset argcnt
 ;       call    ARGC
 ;
 ; ARGV  Returns address and length of specified
 ;       command line argument.  If called for
 ;       argument 0, returns address and length
 ;        of fully qualified pathname of program.
 ;
 ;       Calling sequence:
 ;
 ;       push    argno           ; argument number
 ;       push    seg argptr      ; receives argument address
 ;       push    offset argptr
 ;       push    seg arglen      ; receives argument length
 ;       push    offset arglen
 ;       call    ARGV
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 tab     equ     09h             ; ASCII tab
 blank   equ     20h             ; ASCII space character

         extrn   DosGetEnv:far

 DGROUP  group   _DATA


 _DATA   segment word public 'DATA'

 envseg  dw      ?               ; environment selector
 cmdoffs dw      ?               ; command tail offset

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

                                 ; parameter for argc
 argcnt  equ     [bp+6]          ; receives argument count

         public  argc
 argc    proc    far             ; count command line arguments

         push    bp              ; make arguments addressable
         mov     bp,sp

         push    ds              ; save registers
         push    es
         push    bx
         push    cx

         mov     ax,seg DGROUP   ; point to instance data
         mov     ds,ax

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

         mov     ax,1            ; initialize argument count

 argc0:  inc     bx              ; ignore useless first field
         cmp     byte ptr es:[bx],0
         jne     argc0

 argc1:  mov     cx,-1           ; set flag = outside argument

 argc2:  inc     bx              ; point to next character

         cmp     byte ptr es:[bx],0
         je      argc3           ; exit if null byte

         cmp     byte ptr es:[bx],blank
         je      argc1           ; outside argument if ASCII blank

         cmp     byte ptr es:[bx],tab
         je      argc1           ; outside argument if ASCII tab

                                 ; otherwise not blank or tab,
         jcxz    argc2           ; jump if already inside argument

         inc     ax              ; else found argument, count it
         not     cx              ; set flag = inside argument
         jmp     argc2           ; and look at next character

 argc3:                          ; store result into
                                 ; caller's variable...

         les     bx,argcnt       ; get address of variable
         mov     es:[bx],ax      ; store argument count

         pop     cx              ; restore registers
         pop     bx
         pop     es
         pop     ds
         pop     bp

         xor     ax,ax           ; signal success

         ret     4               ; return to caller
                                 ; and discard parameters
 argc    endp



                                 ; parameters for argv...
 argno   equ     [bp+14]         ; argument number
 argptr  equ     [bp+10]         ; receives argument pointer
 arglen  equ     [bp+6]          ; receives argument length

         public  argv
 argv        proc        far                ; get address and length of
                                 ; command tail argument

         push    bp              ; make arguments addressable
         mov     bp,sp

         push    ds              ; save registers
         push    es
         push    bx
         push    cx
         push    di

         mov     ax,seg DGROUP   ; point to instance data
         mov     ds,ax

         mov     es,envseg       ; set ES:BX = command line
         mov     bx,cmdoffs

         mov     ax,argno        ; get argument number
         or      ax,ax           ; requesting argument 0?
         jz      argv8           ; yes, get program name

 argv1:  inc     bx              ; scan off first field
         cmp     byte ptr es:[bx],0
         jne     argv1

         xor     ah,ah           ; initialize argument counter

 argv2:  mov     cx,-1           ; set flag = outside argument

 argv3:  inc     bx              ; point to next character

         cmp     byte ptr es:[bx],0
         je      argv7           ; exit if null byte

         cmp     byte ptr es:[bx],blank
         je      argv2           ; outside argument if ASCII blank

         cmp     byte ptr es:[bx],tab
         je      argv2           ; outside argument if ASCII tab

                                 ; if not blank or tab...
         jcxz    argv3           ; jump if inside argument

         inc     ah              ; else count arguments found
         cmp     ah,al           ; is this the one?
         je      argv4           ; yes, go find its length

         not     cx              ; no, set flag = inside argument
         jmp     argv3           ; and look at next character

 argv4:                          ; found desired argument, now
                                 ; determine its length...
         mov     ax,bx           ; save param. starting address

 argv5:  inc     bx              ; point to next character

         cmp     byte ptr es:[bx],0
         je      argv6           ; found end if null byte

         cmp     byte ptr es:[bx],blank
         je      argv6           ; found end if ASCII blank

         cmp     byte ptr es:[bx],tab
         jne     argv5           ; found end if ASCII tab

 argv6:  xchg    bx,ax           ; set ES:BX = argument address
         sub     ax,bx           ; and AX = argument length
         jmp     argv10          ; return to caller

 argv7:  mov     ax,1            ; set AX != 0 indicating
                                 ; error, argument not found
         jmp     argv11          ; return to caller

 argv8:                                ; special handling for argv = 0
         xor     di,di           ; find the program name by
         xor     al,al           ; first skipping over all the
         mov     cx,-1           ; environment variables...
         cld

 argv9:        repne scasb                ; scan for double null (can't use SC
         scasb                        ; because it might be odd address)
         jne     argv9           ; loop if it was a single null

         mov     bx,di           ; save program name address
         mov     cx,-1           ; now find its length...
         repne scasb             ; scan for another null byte

         not     cx              ; convert CX to length
         dec     cx
         mov     ax,cx           ; return length in AX

 argv10:                         ; at this point AX = length,
                                 ; ES:BX points to argument

         lds     di,argptr       ; address of 1st variable
         mov     ds:[di],bx      ; store argument pointer
         mov     ds:[di+2],es

         lds        di,arglen        ; address of second variable
         mov     ds:[di],ax      ; store argument length

         xor        ax,ax                ; AX = 0 to signal success

 argv11:                         ; common exit point

         pop     di              ; restore registers
         pop     cx
         pop     bx
         pop     es
         pop     ds
         pop     bp

         ret     10              ; return to caller
                                 ; and discard parameters
 argv    endp


 init    proc    far             ; DLL instance initialization

                                 ; get environment selector
                                 ; and offset of command tail
                                 ; for this process...

         push    seg DGROUP      ; receives environment selector
         push    offset DGROUP:envseg
         push    seg DGROUP      ; receives command tail offset
         push    offset DGROUP:cmdoffs
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; call successful?
         jnz     init1           ; no, initialization error

         mov     ax,1            ; initialization OK,
         ret                     ; return AX = 1 for success

 init1:  xor     ax,ax           ; initialization failed,
         ret                     ; return AX = 0 for error

 init    endp


 _TEXT   ends

         end     init            ; initialization entry point


 BEEPER.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\BEEPER.ASM

         title   BEEPER -- simple multithreaded application
         page    55,132
         .286

 ; BEEPER.ASM  --- A simple multithreaded application which
 ;                 creates a second thread to issue beeps
 ;                 at one-second intervals, while the first
 ;                 thread waits for a any keypress.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM BEEPER;
 ;               LINK BEEPER,,,OS2,BEEPER;
 ;
 ; Usage:        BEEPER

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

 stksiz  equ     4096                    ; stack size for 2nd thread

         extrn   DosAllocSeg:far         ; references to OS/2 API
         extrn   DosBeep:far
         extrn   DosCreateThread:far
         extrn   DosExecPgm:far
         extrn   DosExit:far
         extrn   DosSleep:far
         extrn   DosWrite:far
         extrn   KbdCharIn:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 cdata   db      10 dup (0)              ; receives character data
 wlen    dw      ?                       ; receives bytes written
 selector dw     ?                       ; receives segment selector
 beepID  dw      ?                       ; receives thread ID

 msg1    db      cr,lf
         db      'Press any key to end program...'
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'Unexpected OS/2 Error'
         db      cr,lf
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; entry point for primary thread

                                         ; allocate stack for 2nd thread...
         push    stksiz                  ; size of new segment
         push    ds                      ; receives selector
         push    offset DGROUP:selector
         push    0                       ; 0=not sharable
         call    DosAllocSeg             ; transfer to OS/2
         or      ax,ax                   ; allocation successful?
         jnz     error                   ; jump if failed

                                         ; start new beeper thread...
         push    cs                      ; thread entry point
         push    offset _TEXT:Beeper
         push    ds                      ; receives Thread ID
         push    offset DGROUP:beepID
         push    selector                ; stack for new thread
         push    stksiz
         call    DosCreateThread         ; transfer to OS/2
         or      ax,ax                   ; new thread created?
         jnz     error                   ; jump if failed

                                         ; display message,
                                         ; 'Press any key to end program'...
         push    stdout                  ; standard output handle
         push    ds                      ; address of message
         push    offset msg1
         push    msg1_len                ; length of message
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

                                         ; now wait for key...
         push    ds                      ; receives data packet
         push    offset DGROUP:cdata
         push    0                       ; 0=wait for char.
         push    0                       ; keyboard handle
         call    KbdCharIn               ; transfer to OS/2

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push    0                       ; exit code=0 (success)
         call    DosExit                 ; transfer to OS/2

 error:                                  ; display error message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 main    endp


 beeper  proc    far                     ; thread entry point

                                         ; sound a tone...
         push    440                     ; 440 Hz
         push    100                     ; 100 milliseconds
         call    DosBeep                 ; transfer to OS/2

         push    0                       ; now suspend thread
         push    1000                    ; for 1 second...
         call    DosSleep                ; transfer to OS/2

         jmp     beeper                  ; do it again...

 beeper  endp

 _TEXT   ends

         end     main


 CDLL.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH19\CDLL.C

 /*
         CDLL.C

         C source code for the dynlink library CDLL.LIB.  Requires
         the module CINIT.ASM.

         Compile with:  C> cl /c /Asnu /Gs cdll.c
         Assemble CINIT.ASM with:  C> masm /Mx cinit.asm;
         Link with:  C> link /NOI /NOD cdll+cinit,cdll.dll,,os2,cdll
         Create CDLL.LIB with:  C> implib cdll.lib cdll.def

         Copyright (C) 1988 Ray Duncan
 */

 int _acrtused = 0;            /* don't link startup */

 STDOUT 1            /* standard output handle */

 #define API unsigned extern far pascal

 API DosWrite(unsigned, void far *, unsigned, unsigned far *);

 static char funcmsg[] = "\nCDLL.MYFUNC is executing\n";
 static char initmsg[] = "\nCDLL.C_INIT is executing\n";


 /*
     MYFUNC is exported for use by appliation programs;
     it displays a message and returns the sum of 2 numbers.
 */

 int far pascal MYFUNC(int a, int b)
 {
     unsigned wlen;          /* receives length written */

                             /* display message that
                                MYFUNC is executing */
     DosWrite(STDOUT,funcmsg,sizeof(funcmsg)-1,&wlen);

     return(a+b);            /* return function result */
 }


 /*
     C_INIT is called from the entry point in CINIT.ASM when
     a client process dynamically links to the library.
 */

 int far pascal C_INIT(void)
 {
     unsigned wlen;          /* receives length written */

                             /* display message that
                                C_INIT is executing */
     DosWrite(STDOUT,initmsg,sizeof(initmsg)-1,&wlen);

     return(1);              /* return success code */
 }


 CINIT.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH19\CINIT.ASM

         extrn   C_INIT:far

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT


 INIT    proc    far             ; initialization routine

         call    C_INIT          ; call C routine to
                                 ; do the actual work

         ret                     ; back to caller with
                                 ; AX = status code
                                 ; ("value" from C_INIT)
 INIT    endp

 _TEXT   ends

         end     INIT


 CLEAN.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\CLEAN.C

 /*
     CLEAN.C     Filter to turn document files into
                 normal text files.

     Copyright 1988 Ray Duncan

     Compile:    C>CL CLEAN.C

     Usage:      C>CLEAN  <infile >outfile

     All text characters are passed through with high bit stripped
     off.  Form feeds, carriage returns, and line feeds are passed
     through.  Tabs are expanded to spaces.  All other control codes
     are discarded.
 */

 #include <stdio.h>

 TAB_WIDTH   8               /* width of a tab stop */

 TAB     '\x09'              /* ASCII tab character */
 LF      '\x0A'              /* ASCII line feed */
 FF      '\x0C'              /* ASCII form feed */
 CR      '\x0D'              /* ASCII carriage return */
 BLANK   '\x20'              /* ASCII space code */
 EOFMK   '\x1A'              /* Ctrl-Z end of file */


 main(int argc, char *argv[])
 {
     char c;                         /* char. from stdin */
     int col = 0;                    /* column counter */

     while((c = getchar()) != EOF)   /* read input character */
     {
         c &= 0x07F;                 /* strip high bit */

         switch(c)                   /* decode character */
         {
             case LF:                /* if line feed or */
             case CR:                /* carriage return, */
                 col=0;              /* reset column count */

             case FF:                /* if form feed, carriage */
                 wchar(c);           /* return, or line feed */
                 break;              /* pass character through */

             case TAB:               /* if tab expand to spaces */
                 do wchar(BLANK);
                 while((++col % TAB_WIDTH) != 0);
                 break;

             default:                /* discard other control */
                 if(c >= BLANK)      /* characters, pass text */
                 {                   /* characters through */
                     wchar(c);
                     col++;          /* bump column counter */
                 }
                 break;
         }
     }
     wchar(EOFMK);                   /* write end-of-file mark */
     exit(0);
 }


 /*
     Write a character to the standard output.  If
     write fails, display error message and terminate.
 */

 wchar(char c)
 {
     if((putchar(c) == EOF) && (c != EOFMK))
     {
         fputs("clean: disk full",stderr);
         exit(1);
     }
 }



 CLOCK.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH15\CLOCK.ASM

         title   CLOCK - asynchronous periodic timer example
         page    55,132
         .286
 ;
 ; CLOCK.ASM --- Example of an asynchronous periodic
 ; timer.  Displays the current time at one-second
 ; intervals in the upper-right corner of the screen.
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM CLOCK;
 ;               LINK CLOCK,,,OS2,CLOCK
 ;
 ; Usage:        CLOCK
 ;
 ; Terminate program with Ctrl-C or Ctrl-Break

 stdin   equ     0
 stdout  equ     1
 stderr  equ     2

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed
 escape  equ     01bh                    ; ASCII escape code

         extrn   DosExit:far
         extrn   DosCreateSem:far
         extrn   DosGetInfoSeg:far
         extrn   DosSemWait:far
         extrn   DosSemSet:far
         extrn   DosTimerStart:far
         extrn   DosWrite:far

 jerr    macro   target                  ;; Macro to test return code
         local   zero                    ;; in AX and jump if non-zero
         or      ax,ax                   ;; Uses JMP16 to avoid
         jz      zero                    ;; branch out of range errors
         jmp     target
 zero:
         endm

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 sname   db      '\SEM\CLOCK.SEM',0      ; name of semaphore
 shandle dd      ?                       ; handle of semaphore
 semflag dw      0                       ; <>0 if creator of semaphore

 thandle dw      ?                       ; handle of periodic timer

 wlen    dw      ?                       ; receives actual length written

 ten     db      10                      ; used by b2a conversion routine

 ginfseg dw      ?                       ; global information segment
 linfseg dw      ?                       ; local information segment

 msg1    db      cr,lf,'Unexpected OS/2 Error',cr,lf
 msg1_length equ $-msg1

 msg2    db      escape,'[s'             ; save cursor position
         db      escape,'[0;70H'         ; move to 0,70
 msg2a   db      '00:'                   ; hours
 msg2b   db      '00:'                   ; minutes
 msg2c   db      '00'                    ; seconds
         db      escape,'[u'             ; restore cursor position
 msg2_length equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far

                                         ; get selectors for info segments
         push    ds                      ; receives global selector
         push    offset DGROUP:ginfseg
         push    ds                      ; receives local selector
         push    offset DGROUP:linfseg
         call    DosGetInfoSeg           ; transfer to OS/2
         jerr    main2                   ; jump if couldn't get selectors

                                         ; create system semaphore...
         push    1                       ; exclusive ownership not needed
         push    ds                      ; variable to receive semaphore handl
         push    offset DGROUP:shandle
         push    ds                      ; address of semaphore name
         push    offset DGROUP:sname
         call    DosCreateSem            ; transfer to OS/2
         jerr    main2                   ; jump if sem already exists

         push    0                       ; now create periodic timer...
         push    1000                    ; double word for 1000 msec.
         push    word ptr shandle+2      ; handle of our system semaphore
         push    word ptr shandle
         push    ds                      ; variable to receive timer handle
         push    offset DGROUP:thandle
         call    DosTimerStart           ; transfer to OS/2
         jerr    main2                   ; jump if timer create failed

 main1:                                  ; display time at 1-sec intervals

                                         ; first set our semaphore...
         push    word ptr shandle+2      ; system semaphore handle
         push    word ptr shandle
         call    DosSemSet               ; transfer to OS/2
         jerr    main2                   ; terminate if set failed

         call    display                 ; display current time

         push    word ptr shandle+2      ; wait for 1 second timer
         push    word ptr shandle        ; to be triggered
         push    -1                      ; timeout = -1 = wait indefinitely
         push    -1
         call    DosSemWait              ; transfer to OS/2
         or      ax,ax                   ; did wait fail?
         jz      main1                   ; no, loop to display again

 main2:                                  ; display error message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg1
         push    msg1_length             ; message length
         push    ds                      ; receives actual length written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; final exit to OS/2

 main    endp


 display proc    near                    ; display current time

         mov     es,ginfseg              ; get selector for global
                                         ; read-only information segment

         mov     al,byte ptr es:[8]      ; convert hours to ASCII
         cbw
         div     ten
         add     ax,'00'
         mov     word ptr msg2a,ax

         mov     al,byte ptr es:[9]      ; convert minutes to ASCII
         cbw
         div     ten
         add     ax,'00'
         mov     word ptr msg2b,ax

         mov     al,byte ptr es:[0ah]    ; convert seconds to ASCII
         cbw
         div     ten
         add     ax,'00'
         mov     word ptr msg2c,ax

                                         ; display time...
         push    stdout                  ; standard output handle
         push    ds                      ; address of message
         push    offset msg2
         push    msg2_length             ; length of message
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         ret                             ; back to caller

 display endp

 _TEXT   ends

         end     main


 DASD.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH10\DASD.ASM

         title   DASD --- direct disk access demo
         page    55,132
         .286

 ; DASD.EXE  --- Demonstrates direct disk access under OS/2.
 ;               Reads the boot sector for the current drive
 ;               into a buffer, then writes it into a file
 ;               named \BOOT.BIN on the same drive.
 ;               The lock operation will fail if any files
 ;               are open on the current drive (you can't
 ;               use it on the boot drive, for example).
 ; Copyright (C) Ray Duncan October 1988
 ;
 ; Build:        MASM DASD;
 ;               LINK DASD,,,OS2,DASD;
 ;
 ; Usage:        DASD

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

 secsize equ     512                     ; physical sector size

         extrn   DosChgFilePtr:far       ; references to OS/2 API
         extrn   DosClose:far
         extrn   DosDevIOCtl:far
         extrn   DosExit:far
         extrn   DosOpen:far
         extrn   DosQCurDisk:far
         extrn   DosRead:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 dname   db      'X:',0                  ; logical drive identifier
 dhandle dw      ?                       ; receives drive handle
 daction dw      ?                       ; receives DosOpen action

 drive   dw      0                       ; receives drive ID
 drvmap  dd      0                       ; receives drive bitmap

 fname   db      '\BOOT.BIN',0           ; file to receive boot sector
 rlen    dw      ?                       ; actual bytes read
 wlen    dw      ?                       ; actual bytes written

 bootsec db      secsize dup (?)         ; boot sector read here

 parblk  db      0                       ; DosDevIOCtl dummy parameter

 msg1    db      cr,lf
         db      'DosQCurDisk failed.'
         db      cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'Can''t open drive '
 msg2a   db      'X:'
         db      cr,lf
 msg2_len equ $-msg2

 msg3    db      cr,lf
         db      'Can''t lock drive '
 msg3a   db      'X:'
         db      cr,lf
 msg3_len equ $-msg3

 msg4    db      cr,lf
         db      'Read of boot sector failed.'
         db      cr,lf
 msg4_len equ $-msg4

 msg5    db      cr,lf
         db      'Can''t create file BOOT.BIN'
         db      cr,lf
 msg5_len equ $-msg5

 msg6    db      cr,lf
         db      'Can''t write file BOOT.BIN'
         db      cr,lf
 msg6_len equ $-msg6

 msg7    db      cr,lf
         db      'Boot sector from drive '
 msg7a   db      'X: written into file '
 msg7b   db      'X:BOOT.BIN.'
         db      cr,lf
 msg7_len equ $-msg7

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far

                                         ; get current drive...
         push    ds                      ; receives drive code
         push    offset DGROUP:drive
         push    ds                      ; receives bitmap for
         push    offset DGROUP:drvmap    ; available drives
         call    DosQCurDisk             ; transfer to OS/2
         or      ax,ax                   ; did function succeed?
         jz      main1                   ; yes, proceed

         mov     dx,offset DGROUP:msg1   ; no, display error message
         mov     cx,msg1_len             ; and exit
         jmp     error

 main1:  mov     ax,drive                ; convert drive code to
         add     al,'A'-1                ; ASCII letter and store
         mov     dname,al                ; into drive name string
         mov     msg2a,al                ; and error messages
         mov     msg3a,al
         mov     msg7a,al
         mov     msg7b,al
                                         ; open current drive for
                                         ; direct access ...
         push    ds                      ; address of drive name
         push    offset DGROUP:dname
         push    ds                      ; receives drive handle
         push    offset DGROUP:dhandle
         push    ds                      ; receives DosOpen action
         push    offset DGROUP:daction
         push    0                       ; file allocation (N/A)
         push    0
         push    0                       ; file attribute (N/A)
         push    01h                     ; action: open if exists,
                                         ;         fail if doesn't
         push    8012h                   ; mode: DASD, read/write,
                                         ;       deny all
         push    0                       ; reserved DWORD 0
         push    0
         call    DosOpen                 ; transfer to OS/2
         or      ax,ax                   ; was open successful?
         jz      main2                   ; yes, proceed

         mov     dx,offset DGROUP:msg2   ; no, display error message
         mov     cx,msg2_len             ; and exit
         jmp     error

 main2:                                  ; lock logical drive...
         push    0                       ; data buffer address
         push    0                       ; (not needed)
         push    ds                      ; parameter buffer address
         push    offset DGROUP:parblk
         push    0                       ; function
         push    8                       ; category
         push    dhandle                 ; drive handle
         call    DosDevIOCtl             ; transfer to OS/2
         or      ax,ax                   ; was lock successful?
         jz      main3                   ; yes, proceed

         mov     dx,offset DGROUP:msg3   ; no, display error message
         mov     cx,msg3_len             ; and exit
         jmp     error

 main3:                                  ; now read boot sector...
         push    dhandle                 ; drive handle
         push    ds                      ; buffer address
         push    offset DGROUP:bootsec
         push    secsize                 ; buffer length
         push    ds                      ; receives actual length
         push    offset DGROUP:rlen
         call    DosRead                 ; transfer to OS/2
         or      ax,ax                   ; read successful?
         jnz     main4                   ; no, read failed

         cmp     rlen,secsize            ; actual = expected size?
         je      main5                   ; yes, proceed

 main4:                                  ; read failed...
         mov     dx,offset DGROUP:msg4   ; display error message
         mov     cx,msg4_len             ; and exit
         jmp     error

 main5:                                  ; unlock logical drive...
         push    0                       ; data buffer address
         push    0                       ; (not needed)
         push    ds                      ; parameter buffer address
         push    offset DGROUP:parblk
         push    1                       ; function
         push    8                       ; category
         push    dhandle                 ; drive handle
         call    DosDevIOCtl             ; transfer to OS/2

                                         ; close logical drive...
         push    dhandle                 ; drive handle
         call    DosClose                ; transfer to OS/2

                                         ; now open file BOOT.BIN...
         push    ds                      ; address of filename
         push    offset DGROUP:fname
         push    ds                      ; receives file handle
         push    offset DGROUP:dhandle
         push    ds                      ; receives DosOpen action
         push    offset DGROUP:daction
         push    0                       ; file allocation (N/A)
         push    0
         push    0                       ; file attribute (N/A)
         push    12h                     ; action: create or replace
         push    11h                     ; mode: write-only, deny all
         push    0                       ; reserved DWORD 0
         push    0
         call    DosOpen                 ; transfer to OS/2
         or      ax,ax                   ; was open successful?
         jz      main6                   ; yes, proceed

         mov     dx,offset DGROUP:msg5   ; display error message
         mov     cx,msg5_len             ; and exit
         jmp     error

 main6:                                  ; write sector into BOOT.BIN
         push    dhandle                 ; file handle
         push    ds                      ; address of data
         push    offset DGROUP:bootsec
         push    secsize                 ; length of data
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2
         or      ax,ax                   ; did write succeed?
         jnz     main7                   ; no, display error msg.
         cmp     wlen,secsize            ; was write complete?
         je      main8                   ; yes, jump

 main7:                                  ; write failed...
         mov     dx,offset DGROUP:msg6   ; display error message
         mov     cx,msg6_len             ; and exit
         jmp     error

 main8:  push    dhandle                 ; close the file
         call    DosClose                ; transfer to OS/2

                                         ; display success message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg7
         push    msg7_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 error:                                  ; display error message...
         push    stderr                  ; standard error handle
         push    ds                      ; message address
         push    dx
         push    cx                      ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 main    endp

 _TEXT   ends


         end     main


 DUMBTERM.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\DUMBTERM.ASM

         title        DUMBTERM -- OS/2 Terminal Program
         page    55,132
         .286

 ;
 ; DUMBTERM.ASM
 ;
 ; A simple multithreaded OS/2 terminal program that exchanges
 ; characters between the console and the serial port.
 ;
 ; Communication parameters for COM1 are set at 2400 baud,
 ; 8 data bits, no parity, and 1 stop bit.
 ;
 ; Assemble with:  C> masm dumbterm.asm;
 ; Link with:  C> link dumbterm,,,os2,dumbterm
 ;
 ; Usage is:  C> dumbterm
 ;
 ; Press Alt-X to terminate the program.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 cr      equ     0dh             ; ASCII carriage return
 lf        equ        0ah                ; ASCII linefeed

 kwait   equ     0               ; KbdCharIn parameters
 knowait equ     1

 stksize equ     2048            ; stack size for threads

 exitkey equ     2dh             ; Alt-X key is exit signal

                                 ; COM port configuration
 com1        equ        1                ; nonzero if using COM1
 com2        equ        0                ; nonzero if using COM2
 com3        equ        0                ; nonzero if using COM3
 baud    equ     2400            ; baud rate for COM port
 parity        equ        0                ; 0=N, 1=O, 2=E, 3=M, 4=S
 databit equ     8               ; 5-8
 stopbit equ        0                ; 0=1, 1=1.5, 2=2

         extrn   DosAllocSeg:far ; OS/2 API functions
         extrn   DosClose:far
         extrn   DosCreateThread:far
         extrn   DosDevIOCtl:far
         extrn   DosExit:far
         extrn   DosOpen:far
         extrn   DosRead:far
         extrn   DosSemClear:far
         extrn   DosSemSet:far
         extrn   DosSemWait:far
         extrn   DosSuspendThread:far
         extrn   DosWrite:far
         extrn   KbdCharIn:far
         extrn   KbdGetStatus:far
         extrn   KbdSetStatus:far
         extrn   VioWrtTTY:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 cname   label   byte            ; COM port logical name
         if      com1
         db      'COM1',0        ; COM1 device
         endif
         if      com2
         db      'COM2',0        ; COM2 device
         endif
         if      com3
         db      'COM3',0        ; COM3 device
         endif

 f41info dw      baud            ; baud rate

 f42info db      databit         ; data bits
         db      parity          ; parity
         db      stopbit         ; stop bits

 kbdinfo db      0               ; character code
         db      0               ; scan code
         db      0               ; status flags
         db      0               ; reserved
         dw      0               ; shift state
         dd        0                ; timestamp

 kbdstat dw      10              ; length of structure
         dw      0               ; keyboard state flags
         db      0,0             ; logical end-of-line
         dw      0               ; interim character flags
         dw      0               ; shift state

 exitsem dd      0               ; exit semaphore

 chandle dw      0               ; receives COM device handle
 action  dw      0               ; receives DosOpen action
 sel     dw      0               ; receives selector
 rlen    dw      0               ; receives bytes read count
 wlen    dw      0               ; receives bytes written count
 cinID   dw      0               ; COM input thread ID
 coutID  dw      0               ; COM output thread ID
 inchar        db        0                ; input character from COM port

 msg1    db      cr,lf
         db      "Can't open COM device."
         db      cr,lf
 msg1_len equ    $-msg1

 msg2    db      cr,lf
         db      "Can't set baud rate."
         db      cr,lf
 msg2_len equ    $-msg2

 msg3    db      cr,lf
         db      "Can't configure COM port."
         db      cr,lf
 msg3_len equ    $-msg3

 msg4    db      cr,lf
         db      "Memory allocation failure."
         db      cr,lf
 msg4_len equ    $-msg4

 msg5    db      cr,lf
         db      "Can't create COM input thread."
         db      cr,lf
 msg5_len equ    $-msg5

 msg6    db      cr,lf
         db      "Can't create COM output thread."
         db      cr,lf
 msg6_len equ    $-msg6

 msg7    db      cr,lf
         db      "Communicating..."
         db      cr,lf
 msg7_len equ    $-msg7

 msg8    db      cr,lf
         db      "Terminating..."
         db      cr,lf
 msg8_len equ    $-msg8

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far             ; entry point from OS/2

                                 ; open COM port...
         push    ds              ; device name address
         push    offset DGROUP:cname
         push    ds              ; receives device handle
         push    offset DGROUP:chandle
         push    ds              ; receives action
         push    offset DGROUP:action
         push    0               ; initial allocation (N/A)
         push    0
         push    0               ; attribute (N/A)
         push    1               ; open flag: fail if device
                                 ;            does not exist
         push    12h             ; open mode: read/write,
                                 ;             deny-all
         push    0               ; DWORD reserved
         push    0
         call    DosOpen         ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main1           ; yes, jump

         mov     cx,msg1_len     ; no, display error message
         mov     dx,offset DGROUP:msg1
         jmp     main9           ; and exit

 main1:                          ; set baud rate...
         push    0               ; data buffer address
         push    0
         push    ds              ; parameter buffer address
         push    offset DGROUP:f41info
         push    41h             ; function number
         push    1               ; category number
         push    chandle         ; COM device handle
         call    DosDevIOCtl     ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main2           ; yes, jump

         mov     cx,msg2_len     ; no, display error message
         mov     dx,offset DGROUP:msg2
         jmp     main9           ; and exit

 main2:                          ; configure parity, stop bits,
                                 ; and character length...
         push    0               ; data buffer address
         push    0
         push    ds              ; parameter buffer address
         push    offset DGROUP:f42info
         push    42h             ; function number
         push    1               ; category number
         push    chandle         ; COM device handle
         call    DosDevIOCtl     ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main3           ; yes, jump

         mov     cx,msg3_len     ; no, display error message
         mov     dx,offset DGROUP:msg3
         jmp     main9           ; and exit

 main3:                          ; put keyboard in binary mode
                                 ; with echo off...

                                 ; get keyboard state
         push    ds              ; address of info structure
         push    offset DGROUP:kbdstat
         push    0               ; default keyboard handle
         call    KbdGetStatus    ; transfer to OS/2

                                 ; set binary mode, no echo
         and     word ptr kbdstat+2,0fff0h
         or      word ptr kbdstat+2,6

                                 ; set keyboard state
         push    ds              ; address of info structure
         push    offset DGROUP:kbdstat
         push    0               ; default keyboard handle
         call    KbdSetStatus    ; transfer to OS/2

         push    ds              ; set exit semaphore
         push    offset DGROUP:exitsem
         call    DosSemSet       ; transfer to OS/2

                                 ; allocate thread stack
         push    stksize         ; stack size in bytes
         push    ds              ; receives new selector
         push    offset DGROUP:sel
         push    0               ; not sharable/discardable
         call    DosAllocSeg     ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main4           ; yes, jump

         mov     cx,msg4_len     ; no, display error message
         mov     dx,offset DGROUP:msg4
         jmp     main9           ; and exit

 main4:                          ; create COM input thread
         push    cs              ; thread entry point
         push    offset _TEXT:comin
         push    ds              ; receives thread ID
         push    offset DGROUP:cinID
         push    sel             ; address of stack base
         push    stksize
         call    DosCreateThread ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main5           ; yes, jump

         mov     cx,msg5_len     ; no, display error message
         mov     dx,offset DGROUP:msg5
         jmp     main9           ; and exit

 main5:                          ; allocate thread stack
         push    stksize         ; stack size in bytes
         push    ds              ; receives new selector
         push    offset DGROUP:sel
         push    0               ; not sharable/discardable
         call    DosAllocSeg     ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main6           ; yes, jump

         mov     cx,msg4_len     ; no, display error message
         mov     dx,offset DGROUP:msg4
         jmp     main9           ; and exit

 main6:                          ; create COM output thread
         push    cs              ; thread entry point
         push    offset _TEXT:comout
         push    ds              ; receives thread ID
         push    offset DGROUP:coutID
         push    sel             ; address of stack base
         push    stksize
         call    DosCreateThread ; transfer to OS/2
         or      ax,ax           ; was function successful?
         jz      main7           ; yes, jump

         mov     cx,msg6_len     ; no, display error message
         mov     dx,offset DGROUP:msg6
         jmp     main9           ; and exit

 main7:                          ; display "Communicating..."
         push    ds              ; message address
         push    offset DGROUP:msg7
         push    msg7_len        ; message length
         push    0               ; default video handle
         call    VioWrtTTY       ; transfer to OS/2

                                 ; wait for exit signal
         push    ds              ; semaphore handle
         push    offset DGROUP:exitsem
         push    -1              ; wait indefinitely
         push    -1
         call    DosSemWait      ; transfer to OS/2

                                 ; suspend COM input thread
         push    cinID           ; thread ID
         call    DosSuspendThread ; transfer to OS/2

                                 ; suspend COM output thread
         push    coutID          ; thread ID
         call    DosSuspendThread ; transfer to OS/2

 main8:                                ; display "Terminating"...
         push    ds              ; message address
         push    offset DGROUP:msg8
         push    msg8_len        ; message length
         push    0               ; default video handle
         call    VioWrtTTY       ; transfer to OS/2

                                 ; final exit to OS/2...
         push    1               ; terminate all threads
         push        0                ; return code = 0 (success)
         call    DosExit         ; transfer to OS/2

 main9:                          ; display error message...
                                 ; DS:DX = msg, CX = length
         push    ds              ; address of message
         push    dx
         push    cx              ; length of message
         push    0               ; default video handle
         call    VioWrtTTY       ; transfer to OS/2

                                 ; final exit to OS/2...
         push    1               ; terminate all threads
         push        1                ; return code = 1 (error)
         call    DosExit         ; transfer to OS/2

 main    endp


 ; COM input thread: Reads characters one at a time
 ; from the COM port, and writes them to the display.

 comin   proc    far

                                 ; read character from COM...
         push    chandle         ; COM device handle
         push    ds              ; receives COM data
         push    offset DGROUP:inchar
         push    1               ; length to read
         push    ds              ; receives read count
         push    offset DGROUP:rlen
         call    DosRead         ; transfer to OS/2

                                 ; send character to display...
         push    ds              ; address of character
         push    offset DGROUP:inchar
         push    1               ; length to write
         push    0               ; default video handle
         call    VioWrtTTY       ; transfer to OS/2

         jmp        comin                ; wait for next character

 comin   endp


 ; COM output thread: Reads characters from the keyboard
 ; in raw mode and sends them to the COM port.  If the
 ; exit key is detected, the main thread is signaled to
 ; clean up and terminate the process.

 comout  proc    far

                                 ; read keyboard character...
         push    ds              ; receives keyboard data
         push    offset DGROUP:kbdinfo
         push    kwait           ; wait if necessary
         push    0               ; default keyboard handle
         call    KbdCharIn       ; transfer to OS/2

         mov     cx,1            ; assume writing one byte

         cmp     kbdinfo,0       ; check for extended key
         jz      comout1         ; jump, extended key
         cmp     kbdinfo,0e0h
         jnz     comout2         ; jump, not extended key

 comout1:                        ; extended key detected
         mov     cx,2            ; must write 2 bytes

                                 ; check for exit key
         cmp     kbdinfo+1,exitkey
         jnz     comout2         ; not exit key, jump

                                 ; clear exit semaphore...
         push    ds              ; semaphore address
         push    offset DGROUP:exitsem
         call    DosSemClear     ; transfer to OS/2
         jmp     comout          ; discard exit key

 comout2:                        ; send character to COM port...
         push    chandle         ; COM device handle
         push    ds              ; address of character
         push    offset DGROUP:kbdinfo
         push    cx              ; length to write
         push    ds              ; receives write count
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

         jmp     comout          ; wait for next key

 comout  endp

 _TEXT   ends

         end     main


 DUMBTERM.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\DUMBTERM.C

 /*
         DUMBTERM.C

         A simple multithreaded OS/2 terminal program that exchanges
         characters between the console and the serial port.

         Communication parameters for COM1 are set at 2400 baud,
         8 data bits, no parity, and 1 stop bit.

         Compile with:  C> cl /F 2000 dumbterm.c

         Usage is:  C> dumbterm

         Press Alt-X to terminate the program.

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>

 WAIT   0                        /* parameters for KbdCharIn */
 #define NOWAIT 1

 EXITKEY  0x2d                   /* Exit key = Alt-X */

 STKSIZE 2048                    /* stack size for threads */

 COMPORT "COM1"                  /* COM port configuration */
 BAUD    2400                    /* 110, 150 ... 19200 */
 PARITY        0                        /* 0 = N, 1 = O, 2 = E, 3 = M, 4 = S *
 DATABIT 8                       /* 5-8 allowed */
 STOPBIT 0                        /* 0 = 1, 1 = 1.5, 2 = 2 */

 API unsigned extern far pascal        /* OS/2 API prototypes */

 API DosClose(unsigned);
 API DosCreateThread(void (far *)(), unsigned far *, void far *);
 API DosDevIOCtl(void far *, void far *, unsigned, unsigned, unsigned);
 API DosExit(unsigned, unsigned);
 API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
             unsigned, unsigned, unsigned, unsigned long);
 API DosRead(unsigned, void far *, int, unsigned far *);
 API DosSemClear(unsigned long far *);
 API DosSemSet(unsigned long far *);
 API DosSemWait(unsigned long far *, unsigned long);
 API DosSuspendThread(unsigned);
 API DosWrite(unsigned, void far *, int, unsigned far *);
 API KbdCharIn(void far *, unsigned, unsigned);
 API KbdGetStatus(void far *, unsigned);
 API KbdSetStatus(void far *, unsigned);
 API VioWrtTTY(char far *, int, unsigned);

 void far comin(void);                   /* local function prototypes */
 void far comout(void);
 void errexit(char *);

 struct _F41Info {                       /* DosDevIOCTl info structure */
     int BaudRate;                       /* 110, 150 ... 19200 */
     } F41Info;

 struct _F42Info {                       /* DosDevIOCtl info structure */
     char DataBits;                      /* character length 5-8 bits */
     char Parity;                        /* 0=N, 1=O, 2=E, 3=Mark, 4=Space */
     char StopBits;                      /* 0=1, 1=1.5, 2=2 stop bits */
     } F42Info;

 struct _KbdInfo {                       /* KbdCharIn info structure */
     char CharCode;                      /* ASCII character code */
     char ScanCode;                      /* keyboard scan code */
     char Status;                        /* misc. status flags */
     char Reserved1;                     /* reserved byte */
     unsigned ShiftState;                /* keyboard shift state */
     long TimeStamp;                     /* character timestamp */
     } KbdInfo;

 struct _KbdStatus {                     /* KbdGetStatus info structure */
     int Length;                         /* length of structure */
     unsigned Mask;                      /* keyboard state flags */
     char TurnAround[2];                 /* logical end-of-line */
     unsigned Interim;                   /* interim character flags */
     unsigned ShiftState;                /* keyboard shift state */
     } KbdStatus;

 unsigned chandle;                       /* handle for COM port */
 unsigned long exitsem;                  /* Alt-X exit semaphore */

 main()
 {
     unsigned action;                    /* result of DosOpen */
     unsigned openflag = 0x01;           /* fail if device not found */
     unsigned openmode = 0x12;           /* read/write, deny all */
     unsigned cominID;                   /* COM input thread ID */
     unsigned comoutID;                  /* COM output thread ID */
     char cominstk[STKSIZE];             /* COM input thread stack */
     char comoutstk[STKSIZE];            /* COM output thread stack */

                                         /* open COM port */
     if(DosOpen(COMPORT, &chandle, &action, 0L, 0, openflag, openmode, 0L))
         errexit("\nCan't open COM device.\n");

     F41Info.BaudRate = BAUD;            /* configure COM port */
     F42Info.DataBits = DATABIT;         /* using DosDevIOCtl */
     F42Info.Parity = PARITY;            /* Category 1 functions */
     F42Info.StopBits = STOPBIT;
     if(DosDevIOCtl(NULL, &F41Info, 0x41, 1, chandle))
         errexit("\nCan't set baud rate.\n");
     if(DosDevIOCtl(NULL, &F42Info, 0x42, 1, chandle))
         errexit("\nCan't configure COM port.\n");

     KbdStatus.Length = 10;              /* force keyboard */
     KbdGetStatus(&KbdStatus, 0);        /* into binary mode */
     KbdStatus.Mask &= 0xfff0;           /* with echo off */
     KbdStatus.Mask |= 0x06;
     KbdSetStatus(&KbdStatus, 0);

     exitsem = 0L;                       /* initialize and */
     DosSemSet(&exitsem);                /* set exit semaphore */

                                         /* create COM input thread */
     if(DosCreateThread(comin, &cominID, cominstk+STKSIZE))
         errexit("\nCan't create COM input thread.\n");

                                         /* create COM output thread */
     if(DosCreateThread(comout, &comoutID, comoutstk+STKSIZE))
         errexit("\nCan't create COM output thread.\n");

     puts("\nCommunicating...");         /* sign-on message */

     DosSemWait(&exitsem, -1L);          /* wait for exit signal */

     DosSuspendThread(cominID);          /* freeze COM input and */
     DosSuspendThread(comoutID);         /* output threads */

     puts("\nTerminating...");           /* sign-off message */

     DosExit(1, 0);                      /* terminate all threads */
                                         /* return code = 0 */
 }

 /*
     The 'comin' thread reads characters one at a time from the
     COM port and sends them to the display.
 */
 void far comin(void)
 {
     unsigned rlen;                      /* scratch variable */
     char inchar;                        /* character input buffer */

     while(1)
     {                                        /* read character from COM */
         DosRead(chandle, &inchar, 1, &rlen);
         VioWrtTTY(&inchar, 1, 0);        /* send character to display */
     }
 }

 /*
     The 'comout' thread reads characters one at a time from the
     keyboard and sends them to the COM port.  If the exit key is
     detected, the main thread is signaled to clean up and exit.
 */
 void far comout(void)
 {
     unsigned wlen;                      /* scratch variable */

     while(1)
     {
         KbdCharIn(&KbdInfo, WAIT, 0);   /* read keyboard */
         wlen = 1;                        /* assume 1-byte character */

                                         /* extended character? */
         if((KbdInfo.CharCode == 0) || (KbdInfo.CharCode == 0xe0))
         {
             wlen = 2;                   /* yes, set length = 2 */

             if(KbdInfo.ScanCode == EXITKEY)
             {                           /* if exit key detected */
                 DosSemClear(&exitsem);  /* signal thread 1 to exit */
                 wlen = 0;               /* and discard character */
             }
         }
                                         /* write COM port */
         DosWrite(chandle, &KbdInfo.CharCode, wlen, &wlen);
     }
 }

 /*
     Common error exit routine; displays error message
     and terminates process.
 */
 void errexit(char *msg)
 {
     VioWrtTTY(msg, strlen(msg), 0);     /* display error message */
     DosExit(1, 1);                      /* terminate, exit code = 1 */
 }


 DUMP.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH08\DUMP.ASM

         title        DUMP -- Display File Contents
         page    55,132
         .286

 ;
 ; DUMP.ASM
 ;
 ; Displays the binary contents of a file in hex and ASCII on the
 ; standard output device.
 ;
 ; Assemble with:  C> masm dump.asm;
 ; Link with:  C> link dump,,,os2,dump
 ;
 ; Usage is:  C> dump pathname.ext  [>destination]
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed
 blank   equ     20h                     ; ASCII space code

 blksize equ     16                      ; size of input file records

 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

         extrn   DosOpen:far
         extrn   DosRead:far
         extrn   DosWrite:far
         extrn   DosClose:far
         extrn   DosExit:far

         extrn   argc:near               ; returns argument count
         extrn   argv:near               ; returns argument pointer

 DGROUP  group   _DATA


 _DATA   segment word public 'DATA'

 fname   db      64 dup (0)              ; name of input file

 fhandle dw      0                       ; input file handle

 faction dw      0                       ; action from DosOpen

 fptr    dw      0                       ; relative file address

 rlen    dw      0                       ; actual number of bytes
                                         ; read by DosRead

 wlen    dw      0                       ; actual number of bytes
                                         ; written by DosWrite

 output  db      'nnnn',blank,blank      ; output format area
 outputa db      16 dup ('nn',blank)
         db      blank
 outputb db      16 dup (blank),cr,lf
 output_len equ $-output

 hdg     db      cr,lf
         db      7 dup (blank)
         db      '0  1  2  3  4  5  6  7  '
         db      '8  9  A  B  C  D  E  F',cr,lf
 hdg_len equ $-hdg

 fbuff   db      blksize dup (?)         ; data from file

 msg1    db      cr,lf
         db      'dump: file not found'
         db      cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db        'dump: missing filename'
         db      cr,lf
 msg2_len equ $-msg2

 msg3    db      cr,lf
         db      'dump: empty file'
         db      cr,lf
 msg3_len equ $-msg3

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 dump    proc    far                     ; entry point from OS/2

         push    ds                      ; make DGROUP addressable
         pop     es                      ; via ES

         call    argc                    ; is filename present
         cmp     ax,2                    ; in command tail?
         je      dump1                   ; yes, proceed

         mov     dx,offset msg2          ; missing or illegal filespec,
         mov     cx,msg2_len
         jmp     dump9                   ; display error message and exit

 dump1:                                  ; copy filename from command
                                         ; tail to local buffer

         mov     ax,1                    ; get pointer to command tail
         call    argv                    ; argument in ES:BX
         mov     cx,ax                   ; CX = filename length
         mov     di,offset fname         ; DS:DI = local buffer

 dump2:  mov     al,es:[bx]              ; copy filename byte by byte
         mov     [di],al
         inc     bx
         inc     di
         loop    dump2

         push    ds                      ; restore ES = DGROUP
         pop     es

 dump4:                                  ; try to open file...
         push    ds                      ; address of filename
         push    offset fname
         push    ds                      ; receives file handle
         push    offset fhandle
         push    ds
         push    offset faction          ; receives DosOpen action
         push    0                       ; file size (ignored)
         push    0
         push    0                       ; file attribute (ignored)
         push        1                        ; OpenFlag:
                                         ; fail if file doesn't exist
         push    40h                     ; OpenMode: deny-none,
                                         ; deny-none, access = read-only
         push    0                       ; reserved DWORD 0
         push    0
         call    DosOpen                 ; transfer to OS/2
         or      ax,ax                   ; was open successful?
         jz      dump5                   ; yes, proceed

         mov     dx,offset msg1          ; open failed, display
         mov     cx,msg1_len             ; error message and exit
         jmp     dump9

 dump5:  call    rdblk                   ; initialize file buffer
         cmp     rlen,0                  ; anything read?
         jne     dump6                   ; jump, got some data
         cmp        fptr,0                        ; no data, was this first re
         jne     dump8                   ; no, end of file reached

         mov        dx,offset msg3                ; empty file, print error
         mov     cx,msg3_len             ; message and exit
         jmp     dump9

 dump6:  test    fptr,07fh               ; time for a heading?
         jnz     dump7                   ; no, jump

                                         ; write heading...
         push    stdout                  ; standard output handle
         push    ds                      ; address of heading
         push    offset hdg
         push    hdg_len                 ; length of heading
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite

 dump7:  call    cnvblk                  ; convert one block of
                                         ; binary data to ASCII

                                         ; write formatted output...
         push    stdout                  ; standard output handle
         push    ds                      ; address of output
         push    offset output
         push    output_len              ; length of output
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite

         jmp     dump5                   ; get more data

 dump8:                                  ; end of file reached...
         push    fhandle                 ; close input file
         call    DosClose                ; transfer to OS/2

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push        0                        ; return code = 0 (success)
         call    DosExit                 ; transfer to OS/2

 dump9:                                  ; print error message on
                                         ; standard error device
         push    stderr
         push    ds                      ; address of message
         push    dx
         push    cx                      ; length of message
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push        1                        ; return code = 1 (error)
         call    DosExit                 ; transfer to OS/2

 dump    endp


 ; RDBLK:        Read block of data from input file
 ;
 ; Call with:    nothing
 ; Returns:        AX = error code (0 = no error)
 ; Uses:         nothing

 rdblk   proc    near

         push    fhandle                 ; input file handle
         push    ds                      ; buffer address
         push    offset fbuff
         push    blksize                 ; buffer length
         push    ds                      ; receives bytes read
         push    offset rlen
         call    DosRead                 ; transfer to OS/2
         ret                             ; back to caller

 rdblk   endp


 ; CNVBLK:        Format one binary record for output
 ;
 ; Call with:    nothing
 ; Returns:      nothing
 ; Uses:         AX, BX, CX, DX, DI

 cnvblk  proc    near

         mov     di,offset output        ; clear output format
         mov     cx,output_len-2         ; area to blanks
         mov     al,blank
         rep stosb

         mov     di,offset output        ; convert current file
         mov     ax,fptr                 ; offset to ASCII
         call    wtoa

         xor     bx,bx                   ; point to start of data

 cb1:    mov     al,[fbuff+bx]           ; get next byte of data
                                         ; from input file

         lea        di,[bx+outputb]         ; calculate output address
                                         ; for ASCII equivalent
         mov     byte ptr [di],'.'       ; if control character,
         cmp     al,blank                ; substitute a period
         jb        cb2                        ; jump, not alphanumeric
         cmp     al,7eh
         ja        cb2                        ; jump, not alphanumeric
         mov        [di],al                 ; store ASCII character

 cb2:                                    ; now convert byte to hex
         mov     di,bx                   ; calculate output address
         imul        di,di,3                 ; (position*3) + base address
         add     di,offset outputa
         call    btoa                    ; convert data byte to hex

         inc     bx                      ; advance through record
         cmp     bx,rlen                 ; entire buffer converted?
         jne     cb1                     ; no, get another byte

         add     fptr,blksize            ; update file offset

         ret                             ; back to caller

 cnvblk  endp


 ; WTOA:         Convert word to hex ASCII
 ;
 ; Call with:    AX    = data to convert
 ;               ES:DI = storage address
 ; Returns:      nothing
 ; Uses:         AX, CL, DI

 wtoa    proc    near

         push    ax                      ; save original value
         mov     al,ah
         call    btoa                    ; convert upper byte

         pop     ax                      ; restore original value
         call    btoa                    ; convert lower byte

         ret                             ; back to caller

 wtoa    endp


 ; BTOA:         Convert byte to hex ASCII
 ;
 ; Call with:    AL    = data to convert
 ;               ES:DI = storage address
 ; Returns:      nothing
 ; Uses:         AX, CL, DI

 btoa    proc    near

         sub     ah,ah                   ; clear upper byte

         mov     cl,16                   ; divide by 16
         div     cl

         call    ascii                   ; convert quotient
         stosb                           ; store ASCII character

         mov     al,ah
         call    ascii                   ; convert remainder
         stosb                           ; store ASCII character

         ret                             ; back to caller

 btoa    endp


 ; ASCII:        Convert nibble to hex ASCII
 ;
 ; Call with:    AL    = data to convert in low 4 bits
 ; Returns:      AL    = ASCII character
 ; Uses:         nothing

 ascii   proc    near

         add     al,'0'                  ; add base ASCII value
         cmp     al,'9'                  ; is it in range 0-9?
         jle     ascii2                  ; jump if it is

         add     al,'A'-'9'-1            ; no, adjust for range A-F

 ascii2: ret                                ; return ASCII character in AL

 ascii   endp

 _TEXT   ends

         end     dump


 DUMP.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH08\DUMP.C

 /*
         DUMP.C

         Displays the binary contents of a file in hex and ASCII on
         the standard output device.  Demonstrates direct calls to
         OS/2 API from a C program.

         Compile with:  C> cl dump.c

         Usage is:  C> dump pathname.ext [>destination]

         Copyright (C) 1987 Ray Duncan
 */

 #include <stdio.h>

 RECSIZE 16                  /* size of file records */

 #define API unsigned extern far pascal

 API DosClose(unsigned);             /* function prototypes */

 API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
             unsigned, unsigned, unsigned, unsigned long);

 API DosRead(unsigned, void far *, unsigned, unsigned far *);

 main(int argc, char *argv[])
 {
     char file_buf[RECSIZE];         /* data block from file */
     unsigned long foffset = 0L;     /* file offset in bytes */
     unsigned handle;                /* DosOpen variable */
     unsigned action, length;        /* DosRead variables */
     unsigned flag = 0x01;           /* fail if file not found */
     unsigned mode = 0x40;           /* read only, deny none */

     if(argc < 2)                    /* check command tail */
     {
         fprintf(stderr, "\ndump: missing file name\n");
         exit(1);
     }
                                     /* open file or exit */
     if(DosOpen(argv[1], &handle, &action, 0L, 0, flag, mode, 0L))
     {
         fprintf(stderr, "\ndump: can't find file %s\n", argv[1]);
         exit(1);
     }
                                     /* read and dump records */
     while((DosRead(handle, file_buf, RECSIZE, &length) == 0)
            && (length != 0))
     {
         dump_rec(file_buf, foffset, length);
         foffset += RECSIZE;
     }
     printf("\n");                   /* extra blank line */
     DosClose(handle);               /* close the input file */
     exit(0);                        /* return success code */
 }

 /*
     Display record (16 bytes) in hex and ASCII on standard output.
 */
 dump_rec(char *buffer, long foffset, int length)
 {
     int i;                          /* index to current record */

     if(foffset % 128 == 0)          /* maybe print heading */
         printf("\n\n       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");

     printf("\n%04lX ", foffset);    /* file offset */

     for(i = 0; i < length; i++)     /* print hex equiv. of each byte */
         printf(" %02X", (unsigned char) buffer[i]);

     if(length != 16)                /* space over if last rec. */
         for(i=0; i<(16-length); i++) printf("   ");

     printf("  ");
     for(i = 0; i < length; i++)     /* print ASCII equiv. of bytes */
     {
         if(buffer[i] < 32 || buffer[i] > 126) putchar('.');
         else putchar(buffer[i]);
     }
 }


 EXEC1.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\EXEC1.ASM

         title   EXEC1.ASM Demo of synchronous child process
         page    55,132
         .286
 ;
 ; EXEC1.ASM   Demonstrates use of DosExecPgm to run
 ;             CHKDSK as a synchronous child process.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM EXEC1;
 ;               LINK EXEC1,,,OS2,EXEC1
 ;
 ; Usage:        EXEC1
 ;
 ; Note:         File CHKDSK.COM must be available in PATH.

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

         extrn   DosExecPgm:far          ; reference to OS/2 API
         extrn   DosExit:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 objbuff db      64 dup (0)      ; receives name of dynamic link
 objbuff_len equ $-objbuff       ; causing DosExecPgm failure

                                 ; argument strings for child
 argblk  db      'chkdsk',0      ; simple filename of child
         db      ' *.*',0        ; simulated command tail
         db      0               ; extra null byte terminates block

                                 ; receives return codes from child...
 retcode dw      0               ; termination code for child
         dw      0               ; result code from child's DosExit

 pname   db      'chkdsk.com',0  ; pathname of child program

 wlen    dw      ?

 msg1    db      cr,lf,'Unexpected OS/2 Error',cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf,'Exec function successful',cr,lf
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far

                                         ; run CHKDSK as a syn-
                                         ; chronous child process...
         push    ds                      ; receives module/entry
         push    offset DGROUP:objbuff   ; point if dynlink fails
         push    objbuff_len             ; length of object buffer
         push    0                       ; 0=execute synchronously
         push    ds                      ; address of argument block
         push    offset DGROUP:argblk
         push    0                       ; address of environment
         push    0                       ; (0 = inherit parent's)
         push    ds                      ; receives child's exit
         push    offset DGROUP:retcode   ; and termination codes
         push    ds                      ; name of child program
         push    offset DGROUP:pname
         call    DosExecPgm              ; transfer to OS/2
         or      ax,ax                   ; did child process run?
         jnz     error                   ; jump if function failed

 exit:                                   ; display success message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg2
         push    msg2_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 error:                                  ; display error message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 main    endp

 _TEXT   ends

         end     main



 EXEC2.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\EXEC2.ASM

         title   EXEC2.ASM Demo of asynchronous child process
         page    55,132
         .286
 ;
 ; EXEC2.ASM   Demonstrates use of DosExecPgm and DosCwait to
 ;             run CHKDSK as an asynchronous child process.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM EXEC2;
 ;               LINK EXEC2,,,OS2,EXEC2
 ;
 ; Usage:        EXEC2
 ;
 ; Note:         File CHKDSK.COM must be available in PATH.

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

         extrn   DosCwait:far            ; reference to OS/2 API
         extrn   DosExecPgm:far
         extrn   DosExit:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 objbuff db      64 dup (0)      ; receives name of dynamic link
 objbuff_len equ $-objbuff       ; causing DosExecPgm failure

                                 ; argument strings for child
 argblk  db      'chkdsk',0      ; simple filename of child
         db      ' *.*',0        ; simulated command tail
         db      0               ; extra null byte terminates block

                                 ; receives DosExecPgm info
 cinfo   dw      0               ; PID of child process
         dw      0               ; not used

                                 ; receives DosCwait info
 retcode dw      0               ; termination code for child
         dw      0               ; result code from child's DosExit

 pname   db      'chkdsk.com',0  ; pathname of child program

 wlen    dw      ?

 msg1    db      cr,lf,'Unexpected OS/2 Error',cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf,'Exec function successful',cr,lf
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far

                                         ; run CHKDSK as an asyn-
                                         ; chronous child process...
         push    ds                      ; receives module/entry
         push    offset DGROUP:objbuff   ; point if dynlink fails
         push    objbuff_len             ; length of object buffer
         push    2                       ; 2 = execute asynchronously
         push    ds                      ; address of argument block
         push    offset DGROUP:argblk
         push    0                       ; address of environment
         push    0                       ; (0 = inherit parent's)
         push    ds                      ; receives child's PID
         push    offset DGROUP:cinfo
         push    ds                      ; name of child program
         push    offset DGROUP:pname
         call    DosExecPgm              ; transfer to OS/2
         or      ax,ax                   ; did child process run?
         jnz     error                   ; jump if function failed

                                         ; we could do other
                                         ; other processing here...

                                         ; now resynchronize with
                                         ; child process...
         push    0                       ; 0=immediate child only
         push    0                       ; 0=wait till child ends
         push    ds                      ; receives termination info
         push    offset DGROUP:retcode
         push    ds                      ; receives PID (N/A here)
         push    offset DGROUP:cinfo+2
         push    cinfo                   ; PID of process to wait for
         call    DosCwait                ; transfer to OS/2

 exit:                                   ; display success message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg2
         push    msg2_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 error:                                  ; display error message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 main    endp

 _TEXT   ends

         end     main



 EXEC3.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\EXEC3.ASM

         title   EXEC3.ASM Demo of DosStartSession
         page    55,132
         .286
 ;
 ; EXEC3.ASM  Demonstration of DosStartSession, runs CMD.EXE
 ;            in a new background session to display a directory,
 ;            then waits for a keypress before exiting.
 ;            You can use the Task Manager to switch to the
 ;            new session and end that session by entering EXIT.
 ;            Child session will also be terminated unilaterally
 ;            when you press a key to terminate this process.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM EXEC3;
 ;               LINK EXEC3,,,OS2,EXEC3
 ;
 ; Usage:        EXEC3
 ;
 ; Note:         File CMD.EXE must be available in PATH.

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

         extrn   DosExit:far             ; references to OS/2 API
         extrn   DosStartSession:far
         extrn   DosWrite:far
         extrn   KbdCharIn:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 argstr  db      ' /k dir/w',0           ; argument string for child
 pname   db      'cmd.exe',0             ; pathname for child
 stitle  db      'Child Command Processor',0

 childID dw      0                       ; child process ID
 sesID   dw      0                       ; child session ID

                                         ; session data...
 sinfo   dw      24                      ; length of structure
         dw      1                       ; 0=unrelated, 1=related
         dw      1                       ; 0=foreground, 1=background
         dw      0                       ; 0=nontraceable, 1=traceable
         dd      stitle                  ; session title
         dd      pname                   ; program pathname
         dd      argstr                  ; argument strings
         dd      0                       ; termination queue (not used)

 kbdinfo db      10 dup (0)              ; character input data
 wlen    dw      ?                       ; receives actual bytes written

 msg1    db      cr,lf,'Unexpected OS/2 Error',cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf,'Press any key to terminate parent...',0
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far

                                         ; run CMD.EXE in a new
                                         ; session as a child process
         push    ds                      ; address of session data
         push    offset DGROUP:sinfo
         push    ds                      ; receives new session ID
         push    offset DGROUP:sesID
         push    ds                      ; receives child process ID
         push    offset DGROUP:childID
         call    DosStartSession         ; transfer to OS/2
         or      ax,ax                   ; did call succeed?
         jnz     error                   ; jump if function failed

                                         ; display message,
                                         ; 'Press any key'...
         push    stdout                  ; standard output handle
         push    ds                      ; address of message
         push    offset msg2
         push    msg2_len                ; length of message
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

                                         ; now wait for key...
         push    ds                      ; receives keyboard data
         push    offset DGROUP:kbdinfo
         push    0                       ; 0=wait for character
         push    0                       ; keyboard handle
         call    KbdCharIn               ; transfer to OS/2

 exit:                                   ; final exit to OS/2...
         push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 error:                                  ; display error message
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset wlen
         call    DosWrite                ; transfer to OS/2

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 main    endp

 _TEXT   ends

         end     main


 EXECSORT.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\EXECSORT.ASM

         title          EXECSORT -- Run SORT.EXE as Child
         page      55,132
         .286
         .sall

 ;
 ; EXECSORT.ASM
 ;
 ; Demonstration of use of DosExecPgm to run the OS/2 filter SORT.EXE
 ; as a child process, redirecting its input to MYFILE.DAT and its
 ; output to MYFILE.SRT.
 ;
 ; Assemble with:  C> masm execsort.asm;
 ; Link with:  C> link execsort,,,os2,execsort
 ;
 ; Usage is:  C> execsort
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 stdin   equ     0               ; standard input device
 stdout  equ     1               ; standard output device
 stderr  equ     2               ; standard error device

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed

         extrn   DosClose:far
         extrn   DosDupHandle:far
         extrn   DosExecPgm:far
         extrn   DosExit:far
         extrn   DosOpen:far
         extrn   DosWrite:far

 jerr        macro        target                ;; Macro to test AX
         local        zero                ;; and jump if AX nonzero
         or      ax,ax
         jz        zero                ;; Uses JMP DISP16 to avoid
         jmp        target                ;; branch out of range errors
 zero:
         endm


 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 iname   db      'MYFILE.DAT',0  ; name of input file
 oname   db      'MYFILE.SRT',0  ; name of output file

 ihandle dw      ?               ; handle for input file
 ohandle dw      ?               ; handle for output file

 action  dw      ?               ; receives DosOpen action

 oldin   dw      -1              ; dup of old stdin handle
 oldout  dw      -1              ; dup of old stdout handle

 newin   dw      stdin           ; forced to track ihandle
 newout  dw      stdout          ; forced to track ohandle

 pname   db      'SORT.EXE',0    ; pathname of SORT filter

 objbuff db      64              ; receives failing dynlink
 objbuff_len equ $-objbuff

 pcodes  dw      0,0             ; PID, exit code of child

 msg     db      cr,lf,'SORT was executed as child.',cr,lf
 msg_len equ $-msg

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far             ; entry point from OS/2

                                 ; prepare stdin and stdout
                                 ; handles for child SORT...

                                 ; dup handle for stdin...
         push    stdin           ; standard input handle
         push    ds              ; receives new handle
         push    offset DGROUP:oldin
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; dup handle for stdout...
         push    stdout          ; standard output handle
         push    ds              ; receives new handle
         push    offset DGROUP:oldout
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; open input file...
         push    ds              ; address of filename
         push    offset DGROUP:iname
         push    ds              ; receives file handle
         push    offset DGROUP:ihandle
         push    ds              ; receives DosOpen action
         push    offset DGROUP:action
         push        0                ; file size (not used)
         push    0
         push    0               ; attribute (not used)
         push    1               ; action: open if exists
                                 ;         fail if doesn't
         push    40h             ; access: read-only
         push    0               ; reserved DWORD 0
         push    0
         call    DosOpen         ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; create output file...
         push    ds              ; address of filename
         push    offset DGROUP:oname
         push    ds              ; receives file handle
         push    offset DGROUP:ohandle
         push    ds              ; receives DOSOPEN action
         push    offset DGROUP:action
         push        0                ; initial file size
         push    0
         push    0               ; attribute = normal
         push    12h             ; action: create/replace
         push    41h             ; access: write-only
         push    0               ; reserved DWORD 0
         push    0
         call    DosOpen         ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; make stdin track
                                 ; input file handle...
         push    ihandle         ; handle from DOSOPEN
         push    ds              ; standard input handle
         push    offset DGROUP:newin
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; make stdout track
                                 ; output file handle...
         push    ohandle         ; handle from DOSOPEN
         push    ds              ; standard output handle
         push    offset DGROUP:newout
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; run SORT.EXE as child...
         push    ds              ; receives failing dynlink
         push    offset DGROUP:objbuff
         push    objbuff_len     ; length of buffer
         push    0               ; 0 = synchronous execution
         push    0               ; argument strings ptr
         push    0
         push    0               ; environment pointer
         push    0
         push    ds              ; receives PID, exit code
         push    offset DGROUP:pcodes
         push    ds              ; child program pathname
         push    offset DGROUP:pname
         call    DosExecPgm      ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; restore stdin handle
                                 ; to original meaning...
         push    oldin           ; dup of original stdin
         push    ds              ; standard input handle
         push    offset DGROUP:newin
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; restore stdout handle
                                 ; to original meaning...
         push    oldout          ; dup of original stdout
         push    ds              ; standard output handle
         push    offset DGROUP:newout
         call    DosDupHandle    ; transfer to OS/2
         jerr    main1           ; exit if error

         push    oldin           ; close dup of stdin
         call    DosClose        ; transfer to OS/2
         jerr    main1           ; exit if error

         push    oldout          ; close dup of stdout
         call    DosClose        ; transfer to OS/2
         jerr    main1           ; exit if error

         push    ihandle         ; close input file
         call    DosClose        ; transfer to OS/2
         jerr    main1           ; exit if error

         push    ohandle         ; close output file
         call    DosClose        ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; display success message...
         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:msg
         push    msg_len         ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:action
         call    DosWrite        ; transfer to OS/2
         jerr    main1           ; exit if error

                                 ; exit point if no errors
         push    1               ; terminate all threads
         push    0               ; exit code = 0 (success)
         call    DosExit         ; transfer to OS/2

 main1:                          ; exit point if error
         push    1               ; terminate all threads
         push    1               ; exit code = 1 (error)
         call    DosExit

 main    endp

 _TEXT   ends

         end     main            ; defines entry point


 FIND.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\FIND.C

 /*
         FIND.C

         Searches text stream for a string.

         Compile with:  C> cl find.c

         Usage is:  C> find "pattern" [<source] [>destination]

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>

 TAB     '\x09'                  /* ASCII tab (^I) */
 BLANK   '\x20'                  /* ASCII space */
 TAB_WIDTH 8                     /* columns per tab stop */
 #define BUF_SIZE  256

 static char input[BUF_SIZE];            /* input line buffer */
 static char output[BUF_SIZE];           /* output line buffer */
 static char pattern[BUF_SIZE];          /* search pattern buffer */

 void writeline(int, char *);            /* function prototype */

 main(int argc, char *argv[])
 {
     int line = 0;                       /* initialize line variable */

     if(argc < 2)                        /* search pattern supplied? */
     {
         puts("find: missing pattern");  /* abort if no search pattern */
         exit(1);
     }

     strcpy(pattern,argv[1]);            /* save copy of search pattern */
     strupr(pattern);                    /* fold it to uppercase */

     while(gets(input) != NULL)          /* read a line from input */
     {
         line++;                         /* count lines */

         strcpy(output, input);          /* save copy of input string */
         strupr(input);                  /* fold input to uppercase */

         if(strstr(input, pattern))      /* if line contains pattern */
             writeline(line, output);    /* write it to standard output */
     }
     exit(0);                            /* terminate at end of file */
 }

 /*
     WRITELINE: Write line number and text to standard output,
     expanding any tab characters to stops defined by TAB_WIDTH.
 */

 void writeline(int line, char *p)
 {
     int i = 0;                          /* index to input line */
     int col = 0;                        /* output column counter */

     printf("\n%4d: ", line);            /* write line number */

     while(p[i] != NULL)                 /* while not end of line */
     {
         if(p[i] == TAB)                 /* if tab, expand it */
         {
             do putchar(BLANK);
             while((++col % TAB_WIDTH) != 0);
         }
         else                            /* otherwise leave it alone */
         {
             putchar(p[i]);              /* send character */
             col++;                      /* count columns */
         }
         i++;                            /* advance through input */
     }
 }


 GETENV.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\GETENV.ASM

         title   GETENV get environment string
         page    55,132
         .286

 ; GETENV --- Return address and length of variable
 ;            portion of environment string (OS/2 version).
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Call with:    DS:SI = ASCIIZ env. variable name
 ;
 ; Returns:      ES:DI = address of env. variable
 ;               AX    = length (0 = not found)
 ;
 ; Uses:         nothing

         extrn   DosGetEnv:far   ; OS/2 API function

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT
                                 ; local variables...
 envseg  equ     [bp-2]          ; environment segment
 cmdoffs equ     [bp-4]          ; command line offset

         public  getenv          ; make visible to Linker

 getenv  proc    near

         enter   4,0             ; allocate local variables
         push    cx              ; save registers
         push    si

         push    ss              ; get selector for environment
         lea     ax,envseg       ; and offset of command line
         push    ax
         push    ss
         lea     ax,cmdoffs
         push    ax
         call    DosGetEnv       ; transfer to OS/2
         or      ax,ax           ; did function succeed?
         jz      get1            ; jump if successful

         xor     ax,ax           ; DosGetEnv failed,
         jmp     get5            ; return AX = 0

 get1:   mov     es,envseg       ; set ES:BX = command line
         mov     cx,8000h        ; assume max env. = 32 KB
         xor     di,di           ; initial env. offset
         xor     ax,ax           ; default length result

 get2:                           ; check for end of environment
         cmp     byte ptr es:[di],0
         je      get5            ; end reached, return AX = 0

         pop     si              ; initialize address of target
         push    si              ; variable to be found

         repe cmpsb              ; compare target and env. strings
         cmp     byte ptr [si-1],0
         jne     get3            ; jump if incomplete match
         cmp     byte ptr es:[di-1],'='
         je      get4            ; jump if match was complete

 get3:                           ; match was incomplete
         repne scasb             ; scan for end of env. string
         jmp     get2            ; and try again to match

 get4:   push    di              ; save address after = sign
         repne scasb             ; look for end of this string
         pop     ax              ; get back starting address
         xchg    di,ax           ; find string length
         sub     ax,di
         dec     ax              ; don't include null byte

 get5:                           ; common exit point
         pop     si              ; restore registers
         pop     cx
         leave                   ; discard local variables
         ret                     ; return to caller

 getenv  endp

 _TEXT   ends

         end


 GETENV2.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\GETENV2.ASM

         title   GETENV get environment string
         page    55,132
         .286

 ; GETENV --- Return address and length of variable
 ;            portion of environment string (OS/2 version).
 ;            This version uses OS/2 API function DosScanEnv.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Call with:    DS:SI = ASCIIZ env. variable name
 ;
 ; Returns:      ES:DI = address of env. variable
 ;               AX    = length (0 = not found)
 ;
 ; Uses:         nothing

         extrn   DosScanEnv:far  ; OS/2 API function

 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT
                                 ; local variables...
 valptr  equ     [bp-4]          ; receives pointer to
                                 ; env. value string

         public  getenv          ; make visible to Linker

 getenv  proc    near

         enter   4,0             ; allocate local variable
         push    cx              ; save register

                                 ; call OS/2 to search
                                 ; environment block...
         push    ds              ; address of name string
         push    si
         push    ss              ; address to receive
         lea     ax,valptr       ; pointer to value string
         push    ax
         call    DosScanEnv      ; transfer to OS/2
         or      ax,ax           ; env. variable found?
         jz      get1            ; jump if it exists

         xor     ax,ax           ; else return length=0
         jmp     get2

 get1:                           ; load value string addr.
         les     di,dword ptr valptr

         mov     cx,-1           ; find length of string
         cld                     ; by scanning for null
         xor     al,al
         repnz scasb
         not     cx
         dec     cx              ; and let AX = length,
         mov     ax,cx           ; ES:DI = address
         mov     di,word ptr valptr

 get2:                           ; common exit point
         pop     cx              ; restore registers
         leave                   ; discard local variables
         ret                     ; return to caller

 getenv  endp

 _TEXT   ends

         end


 HELLO.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\HELLO.ASM

         title   HELLO -- Display Message on stdout
         page    55,132
         .286

 ;
 ; HELLO.EXE
 ;
 ; A simple OS/2 assembly language program.
 ;
 ; Copyright (C) 1986 Ray Duncan
 ;

 stdin   equ     0          ; standard input handle
 stdout  equ     1          ; standard output handle
 stderr  equ     2          ; standard error handle

         extrn   DosWrite:far
         extrn   DosExit:far

 DGROUP  group   _DATA


 _DATA   segment word public 'DATA'

 msg     db      0dh,0ah,"Hello World!",0dh,0ah
 msg_len equ     $-msg

 wlen    dw      ?          ; receives bytes written

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 print   proc    far

         push    stdout     ; standard output handle
         push    ds         ; address of data
         push    offset DGROUP:msg
         push    msg_len    ; length of data
         push    ds         ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite   ; transfer to OS/2
         or        ax,ax      ; was write successful?
         jnz        error      ; jump if function failed

         push    1          ; terminate all threads
         push    0          ; return success code
         call    DosExit    ; transfer to OS/2

 error:  push    1          ; terminate all threads
         push    1          ; return error code
         call    DosExit    ; transfer to OS/2

 print   endp

 _TEXT   ends

         end     print



 LC.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\LC.ASM

         title     LC.ASM --- lowercasing filter
         page      55,132
         .286
 ;
 ; LC.ASM        Filter to translate upper case
 ;               chararacters to lower case.
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:        MASM LC;
 ;               LINK LC,,,OS2,LC;
 ;
 ; Usage:        LC [<source] [>destination]

 stdin   equ     0                       ; standard input device
 stdout  equ     1                       ; standard output device
 stderr  equ     2                       ; standard error device

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII linefeed

 bufsize equ     256                     ; max amount to read or write

         extrn   DosExit:far             ; references to OS/2 API
         extrn   DosRead:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 input   db      bufsize dup (?)         ; storage for input line
 output  db      bufsize dup (?)         ; storage for output line

 rlen    dw      ?                       ; receives bytes read
 wlen    dw      ?                       ; receives bytes written

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; entry point from OS/2

         mov     ax,ds                   ; make DGROUP addressable
         mov     es,ax                   ; via ES register too

 main1:                                  ; read line from standard input
         push    stdin                   ; standard input handle
         push    ds                      ; buffer address
         push    offset DGROUP:input
         push    bufsize                 ; buffer length
         push    ds                      ; receives bytes read
         push    offset DGROUP:rlen
         call    DosRead                 ; transfer to OS/2

         mov     ax,rlen                 ; get length of input
         or      ax,ax                   ; any characters read?
         jz      main2                   ; jump if end of stream

         call    translate               ; translate line if necessary

         or      ax,ax                   ; anything to output?
         jz      main1                   ; no, go read another line

                                         ; write line to standard output
         push    stdout                  ; standard output handle
         push    ds                      ; buffer address
         push    offset DGROUP:output
         push    ax                      ; buffer length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         jmp     main1                   ; go read another line

 main2:                                  ; end of stream reached
         push    1                       ; 1 = end all threads
         push    0                       ; 0 = exit code
         call    DosExit                 ; final exit to OS/2

 main    endp                            ; end of main procedure


 ; Copy input buffer to output buffer, translating
 ; any upper case characters to lower case.
 ;
 ; Call with:    AX = length of data in 'input' buffer.
 ;
 ; Return:       AX = length to write to standard output.

 translate proc  near

         push    ax                      ; save original length

         mov     si,offset DGROUP:input  ; address of original line
         mov     di,offset DGROUP:output ; address for converted line
         mov     cx,ax                   ; length to convert
         jcxz    trans3                  ; exit if empty line

 trans1:                                 ; convert char. by char...
         lodsb                           ; get input character
         cmp     al,'A'                  ; if not A-Z, leave it alone
         jb      trans2
         cmp     al,'Z'
         ja      trans2

         add     al,'a'-'A'              ; it's A-Z, convert it

 trans2: stosb                           ; put into output buffer

         loop    trans1                  ; loop until all characters
                                         ; have been transferred

 trans3: pop     ax                      ; get back line length
         ret                             ; and return it in AX

 translate endp

 _TEXT   ends

         end     main


 LC.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\LC.C

 /*
     LC.C    Filter to translate uppercase {A-Z}
             to lowercase {a-z} characters.
     Copyright (C) 1988 Ray Duncan

     Compile:    CL lc.c

     Usage:      LC  [<source]  [>destination]
 */

 #include <stdio.h>

 STDIN   0                   /* standard input handle */
 STDOUT  1                   /* standard output handle */
 BUFSIZE 256                 /* I/O buffer size */

 #define API unsigned extern far pascal

 API DosRead(unsigned, void far *, unsigned, unsigned far *);
 API DosWrite(unsigned, void far *, unsigned, unsigned far *);

 static char input[BUFSIZE];         /* buffer for input line */
 static char output[BUFSIZE];        /* buffer for output line */

 main(int argc,char argv)
 {
     int rlen,wlen;                  /* scratch variables */

     while(1)                        /* do until end of file */
     {                               /* get line from standard input */
         DosRead(STDIN,input,BUFSIZE,&rlen);

         if(rlen == 0) exit(0);      /* exit if end of stream */

                                     /* write translated line
                                        to standard output */
         DosWrite(STDOUT,output,translate(rlen),&wlen);
     }
 }


 /*
     Translate upper case characters to lower
     case characters, leaving resulting text
     in output buffer and returning its length.
 */
 int translate(int length)
 {
     int i;                          /* scratch variable */

     memcpy(output,input,length);    /* copy input to output */

     for(i = 0; i < length; i++)     /* lowercase the output */
     {
         if(output[i] >= 'A' && output[i] <= 'Z')
             output[i] += 'a'-'A';
     }
     return(length);                 /* and return its length */
 }



 MOUDEMO.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH05\MOUDEMO.C

 /*
     MOUDEMO.C   Simple demo of the OS/2 mouse API.
     Copyright (C) 1988 Ray Duncan

     Compile with:  C> cl moudemo.c

     Usage is:  C> moudemo
 */

 #include <stdio.h>

 #define API unsigned extern far pascal

 API MouReadEventQue(void far *, unsigned far *, unsigned);
 API MouOpen(void far *, unsigned far *);
 API MouClose(unsigned);
 API MouDrawPtr(unsigned);
 API VioScrollUp(unsigned, unsigned, unsigned, unsigned,
                 unsigned, char far *, unsigned);
 API VioWrtCharStr(void far *, unsigned, unsigned, unsigned, unsigned);

 struct _MouEventInfo {  unsigned Flags;
                         unsigned long Timestamp;
                         unsigned Row;
                         unsigned Col;
                      }  MouEvent ;

 main(int argc, char *argv[])
 {
     char OutStr[40];                    /* for output formatting */

     unsigned Cell = 0x0720;             /* ASCII space and
                                            normal attribute */

     unsigned MouHandle;                 /* mouse logical handle */
     unsigned Status;                    /* returned from API */
     int WaitOption = 1;                 /* 1 = block for event,
                                            0 = do not block */

     Status = MouOpen(0L, &MouHandle);   /* open mouse device */

     if(Status)                          /* exit if no mouse */
     {
         printf("\nMouOpen failed.\n");
         exit(1);
     }
                                         /* clear the screen */
     VioScrollUp(0, 0, -1, -1, -1, &(char)Cell, 0);

     puts("Press Both Mouse Buttons To Exit");

     MouDrawPtr(MouHandle);              /* display mouse cursor */

     do
     {                                   /* format mouse position */
         sprintf(OutStr, "X=%2d Y=%2d", MouEvent.Col, MouEvent.Row);

                                         /* display mouse position */
         VioWrtCharStr(OutStr, strlen(OutStr), 0, 0, 0);

                                         /* wait for a mouse event */
         MouReadEventQue(&MouEvent, &WaitOption, MouHandle);

                                         /* exit if both buttons down */
     } while((MouEvent.Flags & 0x14) != 0x14) ;

     MouClose(MouHandle);                /* release mouse handle */

     puts("Have a Mice Day!");           /* hail & farewell */
 }


 PORTIO.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH14\C\PORTIO.ASM

         title        PORTIO --  Read/Write I/O Ports
         page    55,132
         .286

 ;
 ; PORTIO.ASM
 ;
 ; General purpose port read/write routines for C or MASM programs.
 ;
 ; Assemble with:  C> masm portio.asm;
 ;
 ; When this module is linked into a program, the following lines
 ; must be present in the program's module definition (DEF) file:
 ;
 ; SEGMENTS
 ;   IO_TEXT IOPL
 ;
 ; EXPORTS
 ;   RPORT 1
 ;   WPORT 2
 ;
 ; The SEGMENTS and EXPORTS directives are recognized by the Linker
 ; and cause information to be built into the EXE file header for
 ; the OS/2 program loader.  The loader is signalled to give I/O
 ; privilege to code executing in the segment IO_TEXT, and to build
 ; call gates for the routines 'RPORT' and 'WPORT'.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 IO_TEXT segment word public 'CODE'

         assume  cs:IO_TEXT


 ; RPORT: Read 8-bit data from I/O port.  Port address
 ; is passed on stack; data is returned in register AX
 ; with AH zeroed.  Other registers are unchanged.
 ;
 ; C syntax:     unsigned port, data;
 ;               data = rport(port);

         public  rport
 rport   proc    far

         push    bp              ; save registers and
         mov     bp,sp           ; set up stack frame
         push    dx

         mov     dx,[bp+6]       ; get port number
         in      al,dx           ; read the port
         xor     ah,ah           ; clear upper 8 bits

         pop     dx              ; restore registers
         pop     bp

         ret     2               ; discard parameters,
                                 ; return port data in AX
 rport   endp


 ; WPORT: Write 8-bit data to I/O port.        Port address and
 ; data are passed on stack.  All registers are unchanged.
 ;
 ; C syntax:     unsigned port, data;
 ;               wport(port, data);

         public  wport
 wport   proc    far

         push    bp              ; save registers and
         mov     bp,sp           ; set up stack frame
         push    ax
         push    dx

         mov     ax,[bp+6]       ; get data to write
         mov     dx,[bp+8]       ; get port number
         out     dx,al           ; write the port

         pop     dx              ; restore registers
         pop     ax
         pop     bp

         ret     4               ; discard parameters,
                                 ; return nothing
 wport   endp

 IO_TEXT ends

         end


 PORTIO.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH14\MASM\PORTIO.ASM

         title        PORTIO --  Read/Write I/O Ports
         page    55,132
         .286

 ;
 ; PORTIO.ASM
 ;
 ; General-purpose port read/write routines for C or MASM programs.
 ;
 ; Assemble with:  C> masm portio.asm;
 ;
 ; When this module is linked into a program, the following lines
 ; must be present in the program's module definition (DEF) file:
 ;
 ; SEGMENTS
 ;   IO_TEXT IOPL
 ;
 ; EXPORTS
 ;   RPORT 1
 ;   WPORT 2
 ;
 ; The SEGMENTS and EXPORTS directives are recognized by the Linker
 ; and cause information to be built into the EXE file header for
 ; the OS/2 program loader.  The loader is signalled to give I/O
 ; privilege to code executing in the segment IO_TEXT, and to build
 ; call gates for the routines 'rport' and 'wport'.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 IO_TEXT segment word public 'CODE'

         assume  cs:IO_TEXT


 ; RPORT: Read 8-bit data from I/O port.  Port address
 ; is passed on stack; data is returned in register AX
 ; with AH zeroed.  Other registers are unchanged.
 ;
 ; C syntax:     unsigned port, data;
 ;               data = rport(port);

         public  rport
 rport   proc    far

         push    bp              ; save registers and
         mov     bp,sp           ; set up stack frame
         push    dx

         mov     dx,[bp+6]       ; get port number
         in      al,dx           ; read the port
         xor     ah,ah           ; clear upper 8 bits

         pop     dx              ; restore registers
         pop     bp

         ret     2               ; discard parameters,
                                 ; return port data in AX
 rport   endp


 ; WPORT: Write 8-bit data to I/O port.        Port address and
 ; data are passed on stack.  All registers are unchanged.
 ;
 ; C syntax:     unsigned port, data;
 ;               wport(port, data);

         public  wport
 wport   proc    far

         push    bp              ; save registers and
         mov     bp,sp           ; set up stack frame
         push    ax
         push    dx

         mov     ax,[bp+6]       ; get data to write
         mov     dx,[bp+8]       ; get port number
         out     dx,al           ; write the port

         pop     dx              ; restore registers
         pop     ax
         pop     bp

         ret     4               ; discard parameters,
                                 ; return nothing
 wport   endp

 IO_TEXT ends

         end


 PORTS.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH14\MASM\PORTS.ASM

         title   PORTS -- IOPL Demo Program
         page    55,132
         .286

 ;
 ; PORTS.ASM
 ;
 ; An OS/2 IOPL demonstration program with that reads and displays the
 ; first 256 I/O ports.        Requires the separate module PORTIO.ASM.
 ;
 ; Assemble with:  C> masm ports.asm;
 ; Link with:  C> link ports+portio,ports,,os2,ports
 ;
 ; Usage is:  C> ports
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 cr      equ     0dh                     ; ASCII carriage return
 lf        equ        0ah                        ; ASCII linefeed
 blank   equ     20h                     ; ASCII space code

 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 bport   equ     0                       ; first port to display
 eport   equ     255                     ; last port to display
 request equ     0                       ; request port access
 release equ     1                       ; release port access

         extrn   DosPortAccess:far       ; kernel API functions
         extrn   DosWrite:far
         extrn   DosExit:far

         extrn   rport:far               ; PORTIO.ASM functions
         extrn   wport:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 wlen    dw      0                       ; actual number of bytes
                                         ; written by DosWrite

 msg1    db      cr,lf                   ; error message
         db      'DosPortAccess failed.'
         db      cr,lf
 msg1_len equ    $-msg1

 msg2    db      cr,lf                   ; heading
         db      '        0  1  2  3  4  5  6'
         db      '  7  8  9  A  B  C  D  E  F'
 msg2_len equ    $-msg2

 msg3    db      cr,lf                   ; display port number
 msg3a   db      'NNNN  '
 msg3_len equ    $-msg3

 msg4    db      ' '                     ; display port data
 msg4a   db      'NN'
 msg4_len equ    $-msg4

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; entry point from OS/2

         push    ds                      ; make DGROUP addressable
         pop     es                      ; with ES too

                                         ; request port access...
         push    0                       ; reserved
         push    request                 ; request/release
         push    bport                   ; first port number
         push    eport                   ; last port number
         call    DosPortAccess           ; transfer to OS/2
         or      ax,ax                   ; call successful?
         jz      main1                   ; yes, jump

                                         ; display error message...
         push    stderr                  ; standard error handle
         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; now terminate process...
         push    1                       ; terminate all threads
         push        1                        ; return code = 1 (error)
         call    DosExit                 ; transfer to OS/2

 main1:                                  ; print heading...
         push    stdout                  ; standard output handle
         push    ds                      ; address of heading
         push    offset DGROUP:msg2
         push    msg2_len                ; length of heading
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         mov     dx,bport                ; initialize port number

 main2:  test    dx,0fh                  ; new line needed?
         jnz     main3                   ; no, jump

         mov     ax,dx                   ; convert port number
         mov     di,offset DGROUP:msg3a  ; to ASCII for display
         call    wtoa

                                         ; display port number...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg3
         push    msg3_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

 main3:  push    dx                      ; call 'rport' to read port
         call    rport                   ; returns AX = data

         mov     di,offset msg4a         ; convert port data
         call    btoa                    ; to ASCII for display

                                         ; display port data...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg4
         push    msg4_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         inc     dx                      ; increment port number
         cmp     dx,eport                ; done with all ports?
         jbe     main2                   ; not yet, read another

                                         ; release port access...
         push    0                       ; reserved
         push    release                 ; request/release
         push    bport                   ; first port number
         push    eport                   ; last port number
         call    DosPortAccess           ; transfer to OS/2

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push        0                        ; return code = 0 (success)
         call    DosExit                 ; transfer to OS/2

 main    endp

 ; WTOA:         Convert word to hex ASCII
 ;
 ; Call with:    AX    = data to convert
 ;               ES:DI = storage address
 ; Returns:      nothing
 ; Uses:         AX, CL, DI

 wtoa    proc    near

         push    ax                      ; save original value
         mov     al,ah
         call    btoa                    ; convert upper byte

         pop     ax                      ; restore original value
         call    btoa                    ; convert lower byte

         ret                             ; back to caller

 wtoa    endp


 ; BTOA:         Convert byte to hex ASCII
 ;
 ; Call with:    AL    = data to convert
 ;               ES:DI = storage address
 ; Returns:      nothing
 ; Uses:         AX, CL, DI

 btoa    proc    near

         sub     ah,ah                   ; clear upper byte

         mov     cl,16                   ; divide by 16
         div     cl

         call    ascii                   ; convert quotient
         stosb                           ; store ASCII character

         mov     al,ah
         call    ascii                   ; convert remainder
         stosb                           ; store ASCII character

         ret                             ; back to caller

 btoa    endp


 ; ASCII:        Convert nibble to hex ASCII
 ;
 ; Call with:    AL    = data to convert in low 4 bits
 ; Returns:      AL    = ASCII character
 ; Uses:         nothing

 ascii   proc    near

         add     al,'0'                  ; add base ASCII value
         cmp     al,'9'                  ; is it in range 0-9?
         jle     ascii2                  ; jump if it is

         add     al,'A'-'9'-1            ; no, adjust for range A-F

 ascii2: ret                                ; return ASCII character in AL

 ascii   endp

 _TEXT   ends

         end     main                    ; defines entry point


 PORTS.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH14\C\PORTS.C

 /*
         PORTS.C

         An OS/2 IOPL demonstration program that reads and displays the
         first 256 I/O ports.  Requires the separate module PORTIO.ASM.

         Compile with:  C> cl /c ports.c
         Link with:  C> link ports+portio,ports,,os2,ports

         Usage is:  C> ports

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>

 #define API extern far pascal

 unsigned API rport(unsigned);           /* function prototypes */
 void     API wport(unsigned, unsigned);
 void     API DosSleep(unsigned long);
 unsigned API DosPortAccess(unsigned, unsigned, unsigned, unsigned);

                                 /* parameters for DosPortAccess */
 REQUEST 0                       /* request port */
 RELEASE 1                       /* release port */
 BPORT   0                       /* beginning port */
 EPORT   255                     /* ending port */

 main(int argc, char *argv[])
 {
     int i;                              /* scratch variable */

                                         /* request port access */
     if(DosPortAccess(0, REQUEST, BPORT, EPORT))
     {
         printf("\nDosPortAccess failed.\n");
         exit(1);
     }

     printf("\n      ");                 /* print title line */
     for(i=0; i<16; i++) printf(" %2X", i);

     for(i=BPORT; i<=EPORT; i++)         /* loop through all ports */
     {
         if((i & 0x0f)==0)
         {
             printf("\n%04X  ", i);      /* new line needed */
         }

         printf(" %02X", rport(i));        /* read and display port */
     }
                                         /* release port access */
     DosPortAccess(0, RELEASE, BPORT, EPORT);
 }



 PROTO.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\PROTO.ASM

         title          PROTO -- Filter Template
         page      55,132
         .286

 ;
 ; PROTO.ASM
 ;
 ; MASM filter template for OS/2.
 ;
 ; Assemble with:  C> masm proto.asm;
 ; Link with:  C> link proto,,,os2;
 ;
 ; Usage is:  C> proto <source >destination
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 stdin   equ     0               ; standard input device
 stdout  equ     1               ; standard output device
 stderr  equ     2               ; standard error device

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII linefeed

 bufsize equ     256             ; I/O buffer size

         extrn   DosRead:far
         extrn   DosWrite:far
         extrn   DosExit:far


 DGROUP  group   _DATA           ; 'automatic data group'

 _DATA   segment word public 'DATA'

 input   db      bufsize dup (?) ; storage for input line
 output  db      bufsize dup (?) ; storage for output line

 rlen    dw      ?               ; receives bytes read
 wlen    dw      ?               ; receives bytes written

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far             ; entry point from OS/2

         push    ds              ; make DGROUP addressable
         pop     es              ; via ES register too
         assume  es:DGROUP

         cld                     ; safety first

 main1:                          ; read line from standard input...
         push    stdin           ; standard input handle
         push    ds              ; buffer address
         push    offset DGROUP:input
         push    bufsize         ; buffer length
         push    ds              ; receives bytes read
         push    offset DGROUP:rlen
         call    DosRead         ; transfer to OS/2
         or      ax,ax           ; was read successful?
         jnz     main3           ; exit if any error

         mov     ax,rlen         ; get length of input
         or      ax,ax           ; any characters read?
         jz      main2           ; jump if end of stream

         call    translate       ; translate line if necessary

         or      ax,ax           ; anything to output?
         jz      main1           ; no, go read another line

                                 ; write line to standard output...
         push    stdout          ; standard output handle
         push    ds              ; buffer address
         push    offset DGROUP:output
         push    ax              ; buffer length
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2
         or      ax,ax           ; write successful?
         jnz     main3           ; exit if any error

         jmp     main1           ; go read another line

 main2:                          ; end of stream reached
         push    1               ; 1 = end all threads
         push    0               ; 0 = exit code
         call    DosExit         ; final exit to OS/2

 main3:                          ; error encountered
         push    1               ; 1 = end all threads
         push    1               ; 1 = exit code
         call    DosExit         ; final exit to OS/2

 main    endp                    ; end of main procedure


 ; Perform any necessary translation on line stored in
 ; 'input' buffer, leaving result in 'output' buffer.
 ;
 ; Call with:        AX = length of data in 'input' buffer
 ;
 ; Return:        AX = length to write to standard output
 ;
 ; Action of template routine is just to copy the line.

 translate proc  near

                                 ; copy input to output...
         mov     si,offset DGROUP:input
         mov     di,offset DGROUP:output
         mov     cx,ax
         rep movsb
         ret                     ; return AX = length unchanged

 translate endp


 _TEXT   ends

         end     main            ; program entry point


 PROTO.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH16\PROTO.C

 /*
         PROTO.C

         A filter template for OS/2.

         Compile with:  C> cl proto.c

         Usage is:  C> proto <source >destination

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>

 STDIN   0                   /* standard input handle */
 STDOUT  1                   /* standard output handle */
 BUFSIZE 256                 /* I/O buffer size */

 #define API unsigned extern far pascal

 API DosRead(unsigned, void far *, unsigned, unsigned far *);
 API DosWrite(unsigned, void far *, unsigned, unsigned far *);

 static char input[BUFSIZE];         /* buffer for input line */
 static char output[BUFSIZE];        /* buffer for output line */

 main(int argc,char *argv[])
 {
     int rlen, wlen;                 /* scratch variables */

     while(1)                        /* do until end of file */
     {
                                     /* get line from standard
                                        input stream */
         if(DosRead(STDIN, input, BUFSIZE, &rlen))
             exit(1);                    /* exit if read error */

         if(rlen == 0) exit(0);      /* exit if end of stream */

                                     /* write translated line to
                                        standard output stream */
         if(DosWrite(STDOUT, output, translate(rlen), &wlen))
             exit(1);                /* exit if write error */
     }
 }

 /*
     Perform any necessary translation on input line,
     leaving the resulting text in output buffer.
     Returns length of translated line (may be zero).
 */

 int translate(int length)
 {
     memcpy(output,input,length);    /* template action is */
                                     /* to copy input line */
     return(length);                 /* and return its length */
 }



 QFN.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\QFN.ASM

         title   QFN.ASM --- qualify file name
         page    55,132
         .286

 ; QFN.ASM -- Qualify File Name (OS/2 Version)
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Call with:    DS:SI = filename address
 ;               AX    = length
 ;
 ; Returns:      Carry = clear if filename OK
 ;               DS:SI = qualified filename
 ;               AX    = length
 ;               or
 ;               Carry = set if bad filename
 ;
 ;               Registers other than DS:SI
 ;               and AX are preserved.

         extrn   DosChDir:far    ; OS/2 API functions
         extrn   DosQCurDisk:far
         extrn   DosQCurDir:far
         extrn   DosSelectDisk:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 cdrive  dw      0               ; current drive
 cpath   db      '\',64 dup (0)  ; current directory
 cpsiz   dw      $-cpath-1
 drvmap  dd      ?               ; valid drive bitmap
 ndrive  dw      0               ; new drive

 tbuff   db      64 dup (0)      ; target directory

 qbuff   db      'X:\'           ; qualified pathname
         db      64 dup (0)
 qbsiz   dw      $-qbuff-3

 fname   dw      ?               ; filename address
 flen    dw      ?               ; filename length

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

         public  qfn             ; make visible to Linker

 qfn     proc    near            ; qualify file name

         push    bx              ; save registers
         push    cx
         push    dx
         push    di
         push    es

         mov     flen,ax         ; save length and
         mov     fname,si        ; address of filename

         mov     ax,ds           ; make DGROUP addressable
         mov     es,ax           ; with ES register

                                 ; save current drive...
         push    ds              ; receives drive ID
         push    offset DGROUP:cdrive
         push    ds              ; receives valid drive bitmap
         push    offset DGROUP:drvmap
         call    DosQCurDisk     ; transfer to OS/2

                                 ; save current directory...
         push    0               ; drive ID = current
         push    ds              ; receives directory path
         push    offset DGROUP:cpath+1
         push    ds              ; contains buffer length
         push    offset DGROUP:cpsiz
         call    DosQCurDir      ; transfer to OS/2

                                 ; did caller specify drive?
         mov     di,fname        ; get address of name
         mov     cx,flen         ; get length of name

         cmp     cx,2            ; if drive, length must
                                 ; be >= 2 chars.
         jl      qfn2            ; too short, no drive

                                 ; check for drive delimiter
         cmp     byte ptr [di+1],':'
         jne     qfn2            ; no delimiter, jump

         mov     al,[di]         ; get ASCII drive code
         or      al,20h          ; fold to lower case
         sub     al,'a'-1        ; convert it to binary
         xor     ah,ah

                                 ; now select new drive
         push    ax              ; binary drive ID
         call    DosSelectDisk   ; transfer to OS/2
         or      ax,ax           ; was drive valid?
         jz      qfn1            ; jump, drive was OK
         jmp     qfn8            ; exit, drive not valid

 qfn1:   add     di,2            ; bump pointer past drive
         sub     cx,2            ; and decrement length

 qfn2:                           ; get current directory
                                 ; again for new drive...
         push    0               ; drive ID = current
         push    ds              ; receives directory path
         push    offset DGROUP:cpath+1
         push    ds              ; contains buffer length
         push    offset DGROUP:cpsiz
         call    DosQCurDir      ; transfer to OS/2

                                 ; scan off path if any
         push    di              ; save start of path
         mov     al,'\'          ; path delimiter

 qfn3:   mov     fname,di        ; save path pointer
         mov     flen,cx         ; save path length
         jcxz    qfn4            ; jump if none left

         repne scasb             ; any '\' left in path?
         je      qfn3            ; loop if '\' found

 qfn4:   pop     si              ; recover starting address
                                 ; of path portion

                                 ; copy path to local buffer
                                 ; and make it ASCIIZ...
         mov     di,offset DGROUP:tbuff
         mov     cx,fname        ; calculate path length
         sub     cx,si
         jz      qfn6            ; jump, no path at all
         cmp     cx,1            ; root directory?
         je      qfn5            ; jump if root
         dec     cx              ; else discard last '\'

 qfn5:   rep movsb               ; transfer path and
         xor     al,al           ; append null byte
         stosb

                                 ; select target directory...
         push    ds              ; directory path address
         push    offset DGROUP:tbuff
         push    0               ; DWORD reserved
         push    0
         call    DosChDir        ; transfer to OS/2
         or      ax,ax           ; directory valid?
         jnz     qfn8            ; jump, no such directory

 qfn6:                           ; build up full pathname...
                                 ; get current drive
         push    ds              ; receives drive ID
         push    offset DGROUP:ndrive
         push    ds              ; receives valid drive bitmap
         push    offset DGROUP:drvmap
         call    DosQCurDisk     ; transfer to OS/2
         mov     ax,ndrive       ; convert binary drive code
         add     ax,'a'-1        ; to ASCII drive identifier
         mov     qbuff,al

                                 ; get current directory
         push    0               ; drive ID = current
         push    ds              ; receives directory path
         push    offset DGROUP:qbuff+3
         push    ds              ; contains buffer length
         push    offset DGROUP:qbsiz
         call    DosQCurDir      ; transfer to OS/2
         or      ax,ax           ; operation successful?
         jnz     qfn8            ; no, return error

                                 ; point to path component
         mov     di,offset DGROUP:qbuff+3
         cmp     byte ptr [di],0 ; is current directory
                                 ; the root directory?
         je      qfn7            ; yes, jump

         xor     al,al           ; scan for null byte at
         mov     cx,-1           ; end of path name
         repne scasb             ; and append backslash
         mov     byte ptr [di-1],'\'

 qfn7:                           ; now append filename
                                 ; to drive and path...
         mov     si,fname        ; filename address
         cmp     byte ptr [si],'.'
         je      qfn8            ; exit if directory alias
         mov     cx,flen         ; filename length
         rep movsb               ; copy it

                                 ; set DS:SI = address
         mov     si,offset DGROUP:qbuff
         mov     ax,di           ; and AX = length of
         sub     ax,si           ; fully qualified filename

         call    makelc          ; fold filename to lower
                                 ; case to make it pretty

         clc                     ; set Carry = false to
                                 ; indicate success and
         jmp     qfn9            ; go to common exit point

 qfn8:                           ; come here if any
                                 ; error detected...
         stc                     ; set Carry = true to
                                 ; indicate error

 qfn9:   pushf                   ; save Carry flag
         push    ax              ; save final length

                                 ; restore original directory
         push    ds              ; address of directory path
         push    offset DGROUP:cpath
         push    0               ; DWORD reserved
         push    0
         call    DosChDir        ; transfer to OS/2

                                 ; restore original drive
         push    cdrive          ; binary drive ID
         call    DosSelectDisk

         pop     ax              ; restore length
         popf                    ; and Carry flag

         pop     es              ; restore other affected
         pop     di              ; registers
         pop     dx
         pop     cx
         pop     bx
         ret                     ; back to caller

 qfn     endp


 makelc  proc    near            ; string -> lower case
                                 ; DS:SI = address
                                 ; AX = length

         push    bx              ; save BX contents
         xor     bx,bx           ; BX will be pointer

 mlc1:                           ; change A-Z to a-z
         cmp     byte ptr [bx+si],'A'
         jb      mlc2
         cmp     byte ptr [bx+si],'Z'
         ja      mlc2
         or      byte ptr [bx+si],20h

 mlc2:   inc     bx              ; advance through string
         cmp     bx,ax           ; done with string yet?
         jne     mlc1            ; no, check next char.

         pop     bx              ; restore BX and
         ret                     ; return to caller

 makelc  endp

 _TEXT   ends

         end



 QFN.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\QFN.C

 /*
     QFN.C   Qualify Filename, OS/2 version
     Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>
 #include <string.h>

 API unsigned extern far pascal  /* OS/2 API functions */

 API DosChDir(char far *, unsigned long);
 API DosQCurDisk(int far *, unsigned long far *);
 API DosQCurDir(int, void far *, int far *);
 API DosSelectDisk(int);

 static char qbuff[80];                  /* receives qualified filename */

 char *qfn(char *p)
 {
     char tbuff[80];                     /* target directory */
     char cpath[80];                     /* current path at entry */
     int cdrive;                         /* current drive at entry */
     int cpsiz = sizeof(cpath);          /* size of path buffer */
     int qbsiz = sizeof(qbuff);          /* size of qual. name buffer */
     unsigned long drvmap;               /* bitmap for valid drives */
     char *q;                            /* scratch pointer */
     int i;                              /* scratch variable */

     DosQCurDisk(&cdrive, &drvmap);      /* get current drive */

     DosQCurDir(0, &cpath[1], &cpsiz);   /* get current directory */
     cpath[0] = '\\';                    /* and prepend backslash */

                                         /* any drive specified? */
     if((strlen(p) >= 2) && (p[1] == ':'))
     {
         i = (p[0] | 0x20)-'a'+1;        /* get binary drive code */

         if(DosSelectDisk(i))            /* switch to new drive */
             goto errexit;               /* return if bad drive */

                                         /* get current directory again */
         DosQCurDir(0, &cpath[1], &cpsiz);

         p += 2;                         /* bump ptr past drive */
     }

     strcpy(tbuff, p);                   /* copy target pathname
                                            to local buffer */

     q = strrchr(tbuff, '\\');           /* look for last backslash */

     if (q != NULL)                      /* any path specified? */
     {
         *q = 0;                         /* yes, make path ASCIIZ */

         if(q == tbuff)                  /* select directory */
         {
             if(DosChDir("\\", 0L))      /* target is root */
                 goto errexit;
         }
         else
         {
             if(DosChDir(tbuff, 0L))     /* target is not root */
             goto errexit;
         }
         q += 1;                         /* point to filename */
     }
     else q = tbuff;                     /* if no path specified,
                                            point to filename */

     if(q[0] == '.') goto errexit;       /* filename may not be
                                            directory alias */

     /* drive and/or path are selected, build qualified filename */

     DosQCurDisk(&i, &drvmap);           /* get target drive */
     qbuff[0] = i+'a'-1;                 /* and convert to ASCII */

     qbuff[1] = ':';                     /* add drive delimiter */
     qbuff[2] = '\\';                    /* and root backslash */

     DosQCurDir(0, &qbuff[3], &qbsiz);   /* get target directory */

     i = strlen(qbuff);                  /* length of drive+path */

     if(i != 3) qbuff[i++] = '\\';       /* if not root, add
                                            trailing backslash */

     strcpy(qbuff+i, q);                 /* copy in filename */

     DosChDir(cpath, 0L);                /* restore original path */
     DosSelectDisk(cdrive);              /* restore original drive */

     return(strlwr(qbuff));              /* fold pathname to lower
                                            case, return pointer */

 errexit:                                /* common error exit point */
     DosChDir(cpath, 0L);                /* restore original path */
     DosSelectDisk(cdrive);              /* restore original drive */
     return(NULL);                       /* return null pointer */
 }



 SCHEX.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\SCHEX.ASM

         title   SCHEX Example directory search program
         page    55,132
         .286

 ; SCH-EX.ASM   Example of OS/2 directory search.
 ;              Finds and displays names of all files
 ;              in current directory with extension .ASM.
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:        MASM SCHEX;
 ;               LINK SCHEX,,,OS2,SCHEX
 ;
 ; Usage:        SCHEX

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

         extrn   DosExit:far
         extrn   DosFindClose:far
         extrn   DosFindFirst:far
         extrn   DosFindNext:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 fname   db      '*.ASM',0               ; target name for search

 sbuf    db      64 dup (?)              ; receives search results
 sbuf_len equ $-sbuf                     ; length of result buffer

 dirhan  dw      ?                       ; receives search handle
 schcnt  dw      ?                       ; receives match count
 wlen    dw      ?                       ; receives bytes written
 counter dw      0                       ; number of files matched

 newline db      cr,lf                   ; carriage return-linefeed
 newline_len equ $-newline

 msg1    db      cr,lf
         db      'No matching files.'
         db      cr,lf
 msg1_len equ $-msg1

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; entry point from OS/2

         mov     dirhan,-1               ; request new search handle
         mov     schcnt,1                ; set max match count

                                         ; search for first match...
         push    ds                      ; search target filename
         push    offset DGROUP:fname
         push    ds                      ; receives search handle
         push    offset DGROUP:dirhan
         push    0                       ; attribute=normal files
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset DGROUP:schcnt
         push    0                       ; reserved DWORD 0
         push    0
         call    DosFindFirst            ; transfer to OS/2

         or      ax,ax                   ; any files found?
         jnz     main2                   ; no, exit

         cmp     schcnt,1                ; confirm match was found
         jne     main2                   ; no, exit

 main1:                                  ; match was found...
         inc     counter                 ; count matching files

                                         ; display one filename...
         push    stdout                  ; standard output handle
         push    ds                      ; file name address
         push    offset DGROUP:sbuf+17h
         mov     al,byte ptr sbuf+16h    ; file name length
         xor     ah,ah
         push    ax
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; send new-line sequence
         push    stdout                  ; standard output handle
         push    ds                      ; new-line sequence address
         push    offset DGROUP:newline
         push    newline_len             ; new-line sequence length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; search for next file...
         push    dirhan                  ; search handle
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset schcnt
         call    DosFindNext             ; transfer to OS/2

         or      ax,ax                   ; any more files?
         jnz     main2                   ; no, exit

         cmp     schcnt,1                ; confirm match was found
         je      main1                   ; yes, proceed

 main2:  push    dirhan                  ; release search handle...
         call    DosFindClose            ; transfer to OS/2

         cmp     counter,0               ; any files found?
         jne     main3                   ; yes

                                         ; no, display 'no matches'
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

 main3:  push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 main    endp

 _TEXT   ends

         end     main



 SHOWARGS.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH19\SHOWARGS.ASM

         title          SHOWARGS -- ASMHELP.DLL Demo
         page      55,132
         .286

 ;
 ; SHOWARGS.ASM
 ;
 ; Demonstrates parsing of command line by calls to ASMHELP.DLL.
 ;
 ; Assemble with:  C> masm showargs.asm;
 ; Link with:  C> link showargs,,,os2,showargs
 ;
 ; Usage is:  C> showargs [argument(s)]
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf        equ        0ah                ; ASCII linefeed
 blank   equ     020h            ; ASCII blank
 tab     equ     09h             ; ASCII tab

                                 ; references to ASMHELP.DLL
         extrn   ARGC:far        ; returns argument count
         extrn   ARGV:far        ; returns pointer to argument

         extrn   DosWrite:far    ; references to OS/2
         extrn   DosExit:far
         extrn   DosGetEnv:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 argno   dw      0               ; receives argument count
 argptr  dw      0,0             ; receives argument pointer
 arglen  dw      0               ; receives argument length

 curarg        dw        0                ; current command line argument

 wlen    dw      0               ; bytes actually written

 msg1    db      cr,lf
         db      'The command line contains '
 msg1a   db      'xx arguments'
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'Argument '
 msg2a   db      'xx is:  '
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far             ; entry point from OS/2

                                 ; get number of command
                                 ;  line arguments...

         push    ds              ; receives argument count
         push    offset DGROUP:argno
         call    argc            ; call ASMHELP.DLL

         mov     ax,argno        ; convert count to ASCII
         mov     bx,offset msg1a ; for output
         call    b2dec

                                 ; now display number
                                 ; of arguments...

         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:msg1
         push    msg1_len        ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

 main1:                          ; display next argument...

         mov     ax,curarg       ; are we all done?
         cmp     ax,argno
         je      main2           ; yes, exit

         mov     bx,offset msg2a ; no, convert argument number
         call    b2dec

                                 ; display argument number...
         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:msg2
         push    msg2_len        ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

                                 ; get argument pointer...
         push    curarg          ; argument number
         push    ds              ; receives pointer
         push    offset DGROUP:argptr
         push    ds              ; receives length
         push    offset DGROUP:arglen
         call    argv            ; call ASMHELP.DLL

                                 ; display the argument...
         push    stdout          ; standard output handle
         push    argptr+2        ; pointer to argument
         push    argptr
         push    arglen          ; length of argument
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

         inc     word ptr curarg ; go to next argument
         jmp     main1

 main2:                          ; common exit point
         push    1               ; terminate all threads
         push    0               ; return code = zero
         call    DosExit         ; transfer to OS/2

 main    endp


 b2dec   proc    near            ; convert binary value 0-99
                                 ;  to two decimal ASCII
                                 ; call with
                                 ; AL = binary data
                                 ; BX = address for 2 chars.

         aam                     ; divide AL by 10, leaving
                                 ; AH = quotient, AL = remainder
         add     ax,'00'         ; convert to ASCII
         mov        [bx],ah         ; store tens digit
         mov        [bx+1],al        ; store ones digit
         ret                     ; return to caller

 b2dec   endp

 _TEXT   ends

         end     main            ; defines entry point


 SHOWARGS.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\SHOWARGS.ASM

         title     SHOWARGS -- ARGC and ARGV demo
         page      55,132
         .286
 ;
 ; SHOWARGS.ASM --- demonstrate command line parsing with
 ;                  ARGC.ASM and ARGV.ASM (OS/2 version)
 ;
 ; Copyright (C) 1987 Ray Duncan
 ;

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed
 blank   equ     020h            ; ASCII blank
 tab     equ     09h             ; ASCII tab

         extrn   argc:near       ; returns argument count
         extrn   argv:near       ; returns argument pointer

                                 ; OS/2 API functions
         extrn   DosWrite:far    ; write file or device
         extrn   DosExit:far     ; terminate process


 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 curarg  dw      0               ; current command line argument
 totargs dw      0               ; total command line arguments

 wlen    dw      ?               ; bytes actually written

 msg1    db      cr,lf
         db      'The command line contains '
 msg1a   db      'xx arguments'
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'Argument '
 msg2a   db      'xx is:  '
 msg2_len equ $-msg2

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far             ; entry point from OS/2

         call    argc            ; get and save number of
         mov     totargs,ax      ; command line arguments

         mov     bx,offset msg1a ; convert argument count
         call    b2dec           ; to ASCII for output

                                 ; display argument count
         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:msg1
         push    msg1_len        ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

 main1:  mov     ax,curarg       ; display next argument
         cmp     ax,totargs      ; are we all done?
         je      main2           ; yes, exit

         mov     bx,offset msg2a ; no, convert argument
         call    b2dec           ; number to ASCII

                                 ; display argument number
         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:msg2
         push    msg2_len        ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

         mov     ax,curarg       ; now get actual argument
         call    argv            ; ES:BX=addr,AX=length

         push    stdout          ; standard output handle
         push    es              ; command argument address
         push    bx
         push    ax              ; command argument length
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

         inc     word ptr curarg ; go to next argument
         jmp     main1

 main2:  push    1               ; terminate process
         push    0               ; return code = zero
         call    DosExit         ; final exit to OS/2

 main    endp


 b2dec   proc    near            ; convert binary 0-99
                                 ; to decimal ASCII
                                 ; call with
                                 ; AL = binary data
                                 ; BX = addr. for 2 chars.

         aam                     ; divide AL by 10, leaving
                                 ; AH=quotient, AL=remainder
         add     ax,'00'         ; convert to ASCII
         mov     [bx],ah         ; store ten's digit
         mov     [bx+1],al       ; store one's digit
         ret                     ; return to caller

 b2dec   endp

 _TEXT   ends

         end     main            ; defines entry point


 SHOWCOM.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH07\SHOWCOM.C

 /*
     SHOWCOM.C --- Simple program to display current COM1
                   setup using the DosDevIOCtl calls.
     Copyright (C) 1987 Ray Duncan

     Build:  C>CL showcom.c
     Usage:  C>SHOWCOM
 */

 #include <stdio.h>

 COMPORT "COM1"              /* COM device name */

 #define API unsigned extern far pascal

 API DosClose(unsigned);
 API DosDevIOCtl(void far *, void far *, unsigned, unsigned, unsigned);
 API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
             unsigned, unsigned, unsigned, unsigned long);

 struct F61Struc {
     int BaudRate;                   /* 110, 150, 300, ... 19200 */
     } F61Info ;

 struct F62Struc {
     unsigned char DataBits;         /* possible values 5-8 */
     unsigned char Parity;           /* 0=N 1=O 2=E 3=Mark 4=Space */
     unsigned char StopBits;         /* 0=1 bit, 1=1.5 bits, 2=2 bits */
     unsigned char Break;            /* 0=not xmitting break */
     } F62Info;

 struct F64Struc {
     unsigned char ComPortStatus;    /* bit  meaning         */
     } F64Info;                      /*  0   Tx waiting for CTS ON
                                         1   Tx waiting for DSR ON
                                         2   Tx waiting for DCD ON
                                         3   Tx waiting, XOFF rcvd
                                         4   Tx waiting, XOFF sent
                                         5   Tx waiting, break xmit
                                         6   Char waiting to xmit
                                         7   Rcv waiting for DSR on  */

 struct F65Struc {
     unsigned char XmitDataStatus;   /* bit  meaning         */
     } F65Info;                      /*  0   write in prog or queued
                                         1   data in xmit queue
                                         2   char xmit in progress
                                         3   char waiting for xmit
                                         4   waiting to send XON
                                         5   waiting to send XOFF
                                         6-7 undefined       */

 struct F66Struc {
     unsigned char OutputSignals;    /* bit  meaning         */
     } F66Info;                      /*  0   DTR (data terminal ready)
                                         1   RTS (request to send)
                                         2-7 undefined       */

 struct F67Struc {
     unsigned char InputSignals;     /* bit  meaning         */
     } F67Info;                      /* 0-3  undefined
                                         4   CTS (clear to send)
                                         5   DSR (data set ready)    */

 struct F68Struc {
     unsigned CharsQueued;           /* chars received and waiting */
     unsigned QueueSize;             /* size of receive char. queue */
     } F68Info;

 struct F69Struc {
     unsigned CharsQueued;           /* chars waiting for xmit */
     unsigned QueueSize;             /* size of transmit char. queue */
     } F69Info;

 struct F6dStruc {
     unsigned ComError;              /* bit  meaning         */
     } F6dInfo;                      /*  0   receive queue overrun
                                         1   receive hardware overrun
                                         2   parity error detected
                                         3   framing error detected
                                         4-15 undefined      */

 struct F72Struc {
     unsigned EventWord;             /* bit  meaning         */
     } F72Info;                      /*  0   set when char read
                                             placed in receive queue
                                         1   undefined
                                         2   set when last char in
                                             transmit queue is sent
                                         3   change in CTS state
                                         4   change in DSR state
                                         5   change in DCD state
                                         6   break detected
                                         7   parity or framing error
                                         8   trailing edge of ring
                                             indicator detected
                                         9-15 undefined      */

 static char *ParityNames[] = { "None", "Odd", "Even", "Mark", "Space" };
 static char *StopNames[]   = { "1", "1.5", "2" };

 main (int argc,char *argv[])
 {
     int action, handle, status;     /* scratch variables */
     int openflag = 0x01;                        /* DosOpen fail if device not
     int openmode = 0x42;            /* DosOpen read/write, deny none */

                                     /* open device or exit */
     if(DosOpen(COMPORT, &handle, &action, 0L, 0, openflag, openmode, 0L))
     {
         fprintf(stderr, "\nCan't open %s device\n", COMPORT);
         exit(1);
     }                               /* got device OK */
     printf("\n%s device opened, handle = %d\n", COMPORT, handle);

                                     /* now display device info */
     status = DosDevIOCtl(&F61Info, NULL, 0x61, 1, handle);
     if(! status) printf("\nBaud rate:          %d", F61Info.BaudRate);

     status = DosDevIOCtl(&F62Info, NULL, 0x62, 1, handle);
     if(! status)
     {
         printf("\nData Bits:          %d", F62Info.DataBits);
         printf("\nParity:             %s", ParityNames[F62Info.Parity]);
         printf("\nStop Bits:          %s", StopNames[F62Info.StopBits]);
     }

     status = DosDevIOCtl(&F64Info, NULL, 0x64, 1, handle);
     if(! status) printf("\nCom Port Status:    %.2xh", F64Info.ComPortStatus)

     status = DosDevIOCtl(&F65Info, NULL, 0x65, 1, handle);
     if(! status) printf("\nTransmit Status:    %.2xh", F65Info.XmitDataStatus

     status = DosDevIOCtl(&F66Info, NULL, 0x66, 1, handle);
     if(! status) printf("\nOutput Signals:     %.2xh", F66Info.OutputSignals)

     status = DosDevIOCtl(&F67Info, NULL, 0x67, 1, handle);
     if(! status) printf("\nInput Signals:      %.2xh", F67Info.InputSignals);

     status = DosDevIOCtl(&F68Info, NULL, 0x68, 1, handle);
     if(! status)
     {
         printf("\nRcv Chars Queued:   %d", F68Info.CharsQueued);
         printf("\nRcv Queue Size:     %d", F68Info.QueueSize);
     }

     status = DosDevIOCtl(&F69Info, NULL, 0x69, 1, handle);
     if(! status)
     {
         printf("\nXmit Chars Queued:  %d", F69Info.CharsQueued);
         printf("\nXmit Queue Size:    %d", F69Info.QueueSize);
     }

     status = DosDevIOCtl(&F6dInfo, NULL, 0x6d, 1, handle);
     if(! status) printf("\nCom Error Info:     %.4xh", F6dInfo.ComError);

     status = DosDevIOCtl(&F72Info, NULL, 0x72, 1, handle);
     if(! status) printf("\nEvent Word:         %.4xh", F72Info.EventWord);

     status = DosClose(handle);      /* an unnecessary nicety */
     if(! status) printf("\n\n%s device closed\n", COMPORT);
 }


 SHOWENV.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH03\SHOWENV.ASM

         title        SHOWENV -- demo of GETENV subroutine
         page    55,132
         .286

 ; SHOWENV.ASM - Demonstrates use of GETENV routine (OS/2 version).
 ;                Prompts user for the name of an environment
 ;                variable, then displays the associated string.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM SHOWENV;
 ;                MASM GETENV;
 ;                LINK SHOWENV+GETENV,,,OS2,SHOWENV
 ;
 ; Usage:        SHOWENV

 stdin        equ        0                ; standard input handle
 stdout        equ        1                ; standard output handle

 cr        equ        0dh               ; ASCII carriage return
 lf        equ        0ah               ; ASCII line feed

         extrn        DosExit:far        ; OS/2 API functions
         extrn        DosRead:far
         extrn        DosWrite:far

         extrn        getenv:near        ; returns address of env. string

 DGROUP        group        _DATA

 _DATA        segment        word public 'DATA'

 msg1        db        cr,lf,lf,'Enter name:    '
 msg1_len equ $-msg1

 msg2        db        cr,lf,   'The value is:  '
 msg2_len equ $-msg2

 msg3        db        'not found!'
 msg3_len equ $-msg3

 inbuf        db        64 dup (0)        ; keyboard input buffer
 inbuf_len equ $-inbuf

 rlen        dw        ?                ; receives actual number
                                 ; of bytes read

 wlen        dw        ?                ; receives actual number
                                 ; of bytes written

 _DATA        ends


 _TEXT        segment        word public 'CODE'

         assume        cs:_TEXT,ds:DGROUP

 main        proc        far               ; entry point from OS/2

                                 ; get env. variable,
                                 ; display cue to user...
         push        stdout                ; standard output handle
         push        ds                ; address of message
         push        offset DGROUP:msg1
         push        msg1_len        ; length of message
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

                                 ; get name of environment
                                 ; variable from user...
         push        stdin                ; standard input handle
         push        ds                ; address of input buffer
         push        offset DGROUP:inbuf
         push        inbuf_len        ; length of input buffer
         push        ds                ; receives actual length
         push        offset DGROUP:rlen
         call        DosRead                ; transfer to OS/2

         mov        ax,rlen                ; get length of input
         sub        ax,2                ; remove cr-lf characters
         or        ax,ax                ; anything entered?
         jz        main2                ; no, exit

         mov        bx,ax                ; append null to string
         mov        byte ptr [bx+inbuf],0

                                 ; display "The value is:"
         push        stdout                ; standard output handle
         push        ds                ; address of message
         push        offset DGROUP:msg2
         push        msg2_len        ; length of message
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

         mov        si,offset inbuf        ; address of ASCIIZ string
         call        strupr                ; fold to upper case
         call        getenv                ; then search environment

         or        ax,ax                ; find anything?
         jz        main1                ; no, display error message

                                 ; yes, display value of
                                 ; environment string
         push        stdout                ; standard output handle
         push        es                ; address of string
         push        di
         push        ax                ; length of string
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

         jmp        main                ; go ask for another

 main1:                                ; env. variable not found
         push        stdout                ; standard output handle
         push        ds                ; address of error message
         push        offset DGROUP:msg3
         push        msg3_len        ; length of message
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

         jmp        main                ; go look for another

 main2:                                ; final exit to OS/2
         push        1                ; terminate all threads
         push        0                ; return code = 0
         call        DosExit                ; transfer to OS/2

 main        endp


 strupr        proc        near                ; convert ASCII string to
                                 ;  upper case
                                 ; call with DS:SI = string
         push        si                ; save string address

 strup1:        lodsb                        ; next character
         or        al,al                ; found end (null byte) ?
         jz        strup2                ; yes, jump
         cmp        al,'a'                ; test if in range 'a'-'z'
         jb        strup1                ; skip it if not >= a
         cmp        al,'z'
         ja        strup1                ; skip it if not <= z
                                 ; change char to lower case
         sub        byte ptr [si-1],'a'-'A'
         jmp        strup1                ; get another char

 strup2:        pop        si                ; restore original string
         ret                        ; address and return

 strupr        endp

 _TEXT        ends

         end        main



 SIGNAL.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH13\SIGNAL.ASM

         title   SIGNAL.ASM Example signal handle
         page    55,132
         .286
 ;
 ; SIGNAL.ASM  Example signal handler for OS/2.
 ;             Registers a signal handler then goes to
 ;             sleep for 10 seconds.  During the sleep
 ;             you can enter ^C and the signal handler
 ;             will display a message.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Build:        MASM SIGNAL;
 ;               LINK SIGNAL,,,OS2,SIGNAL
 ;
 ; Usage:        SIGNAL

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

                                         ; OS/2 signal codes
 SIGINTR         equ     1               ; Ctrl-C
 SIGBROKENPIPE   equ     2               ; broken pipe
 SIGTERM         equ     3               ; program terminated
 SIGBREAK        equ     4               ; Ctrl-Break
 FlagA           equ     5               ; Event flag A
 FlagB           equ     6               ; Event flag B
 FlagC           equ     7               ; Event flag C

         extrn   DosExit:far             ; references to OS/2 API
         extrn   DosFlagProcess:far
         extrn   DosHoldSignal:far
         extrn   DosSetSigHandler:far
         extrn   DosSleep:far
         extrn   DosWrite:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 msg1    db      cr,lf,'Signal detected!',cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'Signal handler installed.'
         db      cr,lf
         db      'Press Ctrl-C to demonstrate handler...'
         db      cr,lf
 msg2_len equ $-msg2

 wlen    dw      ?                       ; receives bytes written

 prvact  dw      ?                       ; receives previous signal action
 prvhdlr dd      ?                       ; receives previous handler address

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; OS/2 entry point

                                         ; register signal handler...
         push    cs                      ; address of handler
         push    offset _TEXT:handler
         push    ds                      ; receives previous handler address
         push    offset DGROUP:prvhdlr
         push    ds                      ; receives previous action
         push    offset DGROUP:prvact
         push    2                       ; action = 2, call handler
         push    SIGINTR                 ; signal of interest= ^C
         call    DosSetSigHandler        ; transfer to OS/2
         or      ax,ax                   ; was set successful?
         jnz     error                   ; jump if set failed.

                                         ; display message 'Press Ctrl-C
                                         ; to demonstrate handler'
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg2
         push    msg2_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         mov     cx,10                   ; delay for 10 seconds...

 main1:  push    0                       ; delay for 1 second
         push    1000
         call    DosSleep

         loop    main1

 exit:   push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; exit program

 error:  push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit

 main    endp


 handler proc    far                     ; this is the signal handler

                                         ; display message showing
                                         ; handler is active...
         push    stdout                  ; standard output handle
         push    ds                      ; address of data to write
         push    offset DGROUP:msg1
         push    msg1_len                ; length of data
         push    ds                      ; receives length written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; now reset signal...
         push    cs                      ; addr of handler
         push    offset _TEXT:handler
         push    ds                      ; receives addr previous handler
         push    offset DGROUP:prvhdlr
         push    ds                      ; receives previous action
         push    offset DGROUP:prvact
         push    4                       ; action = 4; reset signal
         push    SIGINTR                 ; signal of interest= ^C
         call    DosSetSigHandler        ; transfer to OS/2
         or      ax,ax                   ; handler set successfully?
         jnz     error                   ; jump if set failed

         ret     4                       ; return from handler, clear stack

 handler endp

 _TEXT   ends

         end     main



 SIGNAL.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH13\SIGNAL.C

 /*
     SIGNAL.C  Example signal handler in Microsoft C.
               Registers a signal handler then goes to
               sleep for 10 seconds.  During the sleep
               you can enter ^C and the signal handler
               will display a message.

     Copyright (C) 1988 Ray Duncan

     Compile:    CL SIGNAL.C

     Usage:      SIGNAL
 */

 #include <stdio.h>

 SIGINTR 1                       /* Ctrl-C signal number */

 void far pascal handler(unsigned, int); /* function prototypes */

 #define API unsigned extern far pascal

 API DosSetSigHandler(void (pascal far *)(unsigned, int),
                      unsigned long far *, unsigned far *, int, int);
 API DosSleep(unsigned long);

 main()
 {
     unsigned long prevhandler;          /* previous handler address */
     unsigned prevaction;                /* previous handler action */
     int i;                              /* scratch variable */

                                         /* register Ctrl-C handler */
     DosSetSigHandler(handler, &prevhandler, &prevaction, 2, SIGINTR);

     fprintf(stderr,"\nSignal handler registered.");
     fprintf(stderr,"\nEnter Ctrl-C to demonstrate handler.\n");

     for(i=0; i<10; i++)                 /* sleep for 10 seconds */
         DosSleep(1000L);
 }

 void far pascal handler(unsigned sigarg, int signum)
 {
     unsigned long dummy1;               /* scratch variables */
     unsigned dummy2;

                                         /* reset signal */
     DosSetSigHandler(handler, &dummy1, &dummy2, 4, SIGINTR);

                                         /* show something happened */
     fprintf(stderr,"\nSignal Received!\n");
 }



 SNAP.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH18\SNAP.ASM

         title   SNAP -- Sample OS/2 Device Monitor
         page    55,132
         .286

 ;
 ; SNAP.ASM
 ;
 ; A sample OS/2 device monitor that captures the current display into
 ; the file SNAPxx.IMG, where xx is the session number.  SNAP works in
 ; character mode only and may not be used in a PM window.  The
 ; following keys are defined as defaults:
 ;
 ; Alt-F10   hot key to capture a screen
 ; Ctrl-F10  hot key to deinstall SNAP.EXE
 ;
 ; Assemble with:  C> masm snap.asm;
 ; Link with:  C> link snap,,,os2,snap
 ;
 ; Usage is:  C> snap
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 cr      equ     0dh                     ; ASCII character codes
 lf      equ     0ah

                                         ; hot key definitions:
 snapkey equ     71h                     ; snapshot   Alt-F10
 exitkey equ     67h                     ; exit       Ctrl-F10

 stksize equ     2048                    ; stack size for threads

         extrn   DosAllocSeg:far
         extrn   DosBeep:far
         extrn   DosBufReset:far
         extrn   DosClose:far
         extrn   DosCloseSem:far
         extrn   DosCreateSem:far
         extrn   DosCreateThread:far
         extrn   DosExecPgm:far
         extrn   DosExit:far
         extrn   DosGetInfoSeg:far
         extrn   DosOpenSem:far
         extrn   DosMonClose:far
         extrn   DosMonOpen:far
         extrn   DosMonRead:far
         extrn   DosMonReg:far
         extrn   DosMonWrite:far
         extrn   DosOpen:far
         extrn   DosSemClear:far
         extrn   DosSemSet:far
         extrn   DosSemWait:far
         extrn   DosSetPrty:far
         extrn   DosSleep:far
         extrn   DosSuspendThread:far
         extrn   DosWrite:far
         extrn   VioEndPopUp:far
         extrn   VioGetMode:far
         extrn   VioPopUp:far
         extrn   VioReadCharStr:far
         extrn   VioWrtCharStr:far

 jerr    macro   p1,p2,p3                ;; Macro to test return code
         local        zero                        ;; in AX and jump if nonzero
         or      ax,ax                   ;; Uses JMP DISP16 to avoid
         jz      zero                    ;; branch out of range errors
         mov     dx,offset DGROUP:p2     ;; p2 = message address
         mov     cx,p3                   ;; p3 = message length
         jmp     p1                      ;; routine p1 displays message
 zero:
         endm

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 exitsem dd      0                       ; semaphore for final exit
 snapsem dd      0                       ; semaphore for 'snap' thread

 sname   db      '\SEM\SNAP'             ; system semaphore name
 sname1  db      'nn.LCK',0
 shandle dd      0                       ; system semaphore handle

 pflags  dw      0                       ; VioPopUp flags

 wlen    dw      ?                       ; receives length written
 action  dw      ?                       ; receives DosOpen action

 watchID dw      ?                       ; keyboard thread ID
 snapID  dw      ?                       ; snapshot thread ID

 sel     dw      ?                       ; selector from DosAllocSeg

 kname   db      'KBD$',0                ; keyboard device name
 khandle dw      0                       ; keyboard monitor handle

 fname   db      '\SNAP'                 ; name of snapshot file
 fname1  db      'nn.IMG',0
 fhandle dw      0                       ; handle for snapshot file

 scrbuf  db      80 dup (0)              ; receives screen data
 slen    dw      $-scrbuf                ; length of screen buffer

 newline db      cr,lf                   ; carriage return-linefeed
 nl_len  equ     $-newline

 gseg        dw        ?                        ; global info segment selector
 lseg        dw        ?                        ; local info segment selector

 obuff        db        64 dup (0)                ; receives name of dynlink
 obuff_len equ   $-obuff                 ; causing DosExecPgm to fail

 kbdin   dw      128,64 dup (0)          ; input and output buffers
 kbdout  dw      128,64 dup (0)          ; for keyboard monitor

 kbdpkt  db      128 dup (0)             ; keyboard data packet
 kpktlen dw      ?                       ; length of buffer/packet

 pname   db      'SNAP.EXE',0            ; child process name
 retcode dd      0                       ; child process info

 vioinfo label   byte                    ; receives display mode
         dw      8                       ; length of structure
         db      0                       ; display mode type
         db      0                       ; colors
 cols    dw      0                       ; number of columns
 rows    dw      0                       ; number of rows

 msg1    db      'SNAP utility installed!'
 msg1_len equ    $-msg1

 msg2    db      'Alt-F10 to capture screen into file SNAP.IMG,'
 msg2_len equ    $-msg2

 msg3    db      'Ctrl-F10 to shut down SNAP.'
 msg3_len equ    $-msg3

 msg4    db      'SNAP utility deactivated.'
 msg4_len equ    $-msg4

 msg5    db      'Error detected during SNAP installation:'
 msg5_len equ    $-msg5

 msg6    db      'Can''t create SNAP system semaphore.'
 msg6_len equ    $-msg6

 msg7    db      'Can''t start child copy of SNAP.'
 msg7_len equ    $-msg7

 msg8    db      'SNAP is already loaded.'
 msg8_len equ    $-msg8

 msg9    db      'Can''t open KBD$ monitor connection.'
 msg9_len equ    $-msg9

 msg10   db      'Can''t register as KBD$ monitor.'
 msg10_len equ   $-msg10

 msg11   db      'Can''t allocate thread stack.'
 msg11_len equ   $-msg11

 msg12   db      'Can''t create keyboard thread.'
 msg12_len equ   $-msg12

 msg13   db      'Can''t create snapshot thread.'
 msg13_len equ   $-msg13

 msg14   db      'Can''t create snapshot file.'
 msg14_len equ   $-msg14

 divider db      79 dup ('-'),cr,lf
 divider_len equ $-divider

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 main    proc    far                     ; entry point from OS/2

                                         ; get info segment selectors
         push        ds                        ; receives global info selector
         push    offset DGROUP:gseg
         push        ds                        ; receives local info selector
         push    offset DGROUP:lseg
         call    DosGetInfoSeg           ; transfer to OS/2

                                         ; build system semaphore
                                         ; and snapshot file names
         mov     es,gseg                 ; get foreground screen group
         mov     al,es:[0018h]
         aam                             ; convert to ASCII
         add     ax,'00'
         xchg    ah,al
         mov     word ptr fname1,ax      ; store into filename
         mov     word ptr sname1,ax      ; store into semaphore name

                                         ; does SNAPxx.LCK exist?
         push    ds                      ; receives semaphore handle
         push    offset DGROUP:shandle
         push    ds
         push    offset DGROUP:sname     ; semaphore name
         call    DosOpenSem              ; transfer to OS/2
         or      ax,ax                   ; was open successful?
         jz      main1                   ; jump, we're child SNAP

                                         ; we're the parent SNAP,
                                         ; create system semaphore
         push        1                        ; make it nonexclusive
         push    ds                      ; receives semaphore handle
         push    offset DGROUP:shandle
         push    ds                      ; system semaphore name
         push    offset DGROUP:sname
         call    DosCreateSem            ; transfer to OS/2
         jerr    error,msg6,msg6_len     ; jump if create failed

                                         ; set the semaphore...
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosSemSet               ; transfer to OS/2

                                         ; launch child SNAP...
         push    ds                      ; object name buffer
         push    offset DGROUP:obuff     ; receives failed dynlink
         push    obuff_len               ; length of buffer
         push    4                       ; child detached
         push    0                       ; NULL argument pointer
         push    0
         push    0                       ; NULL environment pointer
         push    0
         push    ds                      ; receives child info
         push    offset DGROUP:retcode
         push    ds                      ; pathname for child
         push    offset DGROUP:pname
         call    DosExecPgm              ; request launch of child
         jerr    error,msg7,msg7_len     ; jump if launch failed

                                         ; wait for child to load
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         push    -1                      ; timeout = indefinite
         push    -1
         call    DosSemWait              ; transfer to OS/2

                                         ; close the semaphore...
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosCloseSem             ; transfer to OS/2

         jmp     main3                   ; now exit

 main1:                                  ; come here if child SNAP...
                                         ; check if already resident
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         push    0                       ; timeout = 0
         push    0
         call    DosSemWait              ; transfer to OS/2
         or      ax,ax                   ; is semaphore clear?
         jnz     main2                   ; no, proceed

                                         ; yes, don't load again
         mov     dx,offset DGROUP:msg8   ; address of warning message
         mov     cx,msg8_len             ; length of message
         jmp     error                   ; display message and exit

 main2:                                  ; initialize semaphores...
         push    ds                      ; address of exit semaphore
         push    offset DGROUP:exitsem
         call    DosSemSet               ; transfer to OS/2

         push    ds                      ; address of snapshot semaphore
         push    offset DGROUP:snapsem
         call    DosSemSet               ; transfer to OS/2

                                         ; open monitor connection ...
         push    ds                      ; address of device name
         push    offset DGROUP:kname
         push    ds                      ; receives monitor handle
         push    offset DGROUP:khandle
         call    DosMonOpen              ; transfer to OS/2
         jerr    error,msg9,msg9_len     ; jump if open failed

                                         ; register as keyboard monitor
         push    khandle                 ; handle from DosMonOpen
         push    ds                      ; monitor input buffer address
         push    offset DGROUP:kbdin
         push    ds                      ; monitor output buffer address
         push    offset DGROUP:kbdout
         push    1                       ; position = front of list
         mov     es,gseg                 ; foreground session number
         mov     al,byte ptr es:[0018h]  ; from global info segment
         xor     ah,ah
         push    ax
         call    DosMonReg               ; transfer to OS/2
         jerr    error,msg10,msg10_len   ; jump if register failed

         push    stksize                 ; allocate stack for WATCH thread
         push    ds                      ; variable to receive selector
         push    offset DGROUP:sel
         push        0                        ; not shareable
         call    DosAllocSeg             ; transfer to OS/2
         jerr    error,msg11,msg11_len   ; jump, can't allocate stack

                                         ; create keyboard thread
         push    cs                      ; initial execution address
         push    offset _TEXT:watch
         push    ds                      ; receives thread ID
         push    offset DGROUP:watchID
         push    sel                     ; address of thread's stack
         push    stksize
         call    DosCreateThread         ; transfer to OS/2
         jerr    error,msg12,msg12_len   ; jump, can't create thread

                                         ; promote keyboard thread
         push    2                       ; scope = single thread
         push    3                       ; class = time critical
         push    0                       ; delta = 0
         push    watchID                 ; thread ID
         call    DosSetPrty              ; transfer to OS/2

         push    stksize                 ; allocate stack for SNAP thread
         push    ds                      ; variable to receive selector
         push    offset DGROUP:sel
         push        0                        ; not shareable
         call    DosAllocSeg             ; transfer to OS/2
         jerr    error,msg11,msg11_len   ; jump, can't allocate stack

                                         ; create snapshot thread
         push    cs                      ; initial execution address
         push    offset _TEXT:snap
         push    ds                      ; receives thread ID
         push    offset DGROUP:snapID
         push    sel                     ; address of thread's stack
         push    stksize
         call    DosCreateThread         ; transfer to OS/2
         jerr    error,msg13,msg13_len   ; jump, can't create thread

         call    signon                  ; announce installation

                                         ; tell parent we are running
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosSemClear             ; transfer to OS/2

                                         ; block on exit semaphore...
         push    ds                      ; semaphore handle
         push    offset DGROUP:exitsem
         push    -1                      ; timeout = indefinite
         push    -1
         call    DosSemWait              ; transfer to OS/2

         push    watchID                 ; suspend keyboard thread
         call    DosSuspendThread        ; transfer to OS/2

         push    snapID                  ; suspend snapshot thread
         call    DosSuspendThread        ; transfer to OS/2

                                         ; close monitor connection
         push    khandle                 ; monitor handle
         call    DosMonClose             ; transfer to OS/2

                                         ; close system semaphore
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosCloseSem             ; transfer to OS/2

                                         ; close snapshot file
         push    fhandle                 ; file handle
         call    DosClose                ; transfer to OS/2

         call        signoff                 ; announce deinstallation

 main3:  push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; final exit to OS/2

 main    endp


 error   proc    near                    ; fatal error encountered
                                         ; DS:DX = message, CX = length

         test    khandle,-1              ; monitor active?
         jz      error1                  ; no, jump

                                         ; yes, shut it down
         push    khandle                 ; monitor handle
         call    DosMonClose             ; transfer to OS/2

 error1: mov     ax,word ptr shandle     ; system semaphore open?
         or      ax,word ptr shandle+2
         jz      error2                  ; no, jump

                                         ; clear semaphore, in case
                                         ; we're the child SNAP
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosSemClear             ; transfer to OS/2

                                         ; close the semaphore
         push    word ptr shandle+2      ; semaphore handle
         push    word ptr shandle
         call    DosCloseSem             ; transfer to OS/2

 error2: mov     ax,1                    ; get popup window
         call    popup

                                         ; display title...
         push    ds                      ; message address
         push    offset DGROUP:msg5
         push    msg5_len                ; message length
         push    10                      ; Y
         push    (80-msg5_len)/2         ; X (center it)
         push        0                        ; Vio handle
         call    VioWrtCharStr           ; transfer to OS/2

                                         ; display error message...
         push    ds                      ; message address
         push    dx
         push    cx                      ; message length
         push    12                      ; Y
         mov     ax,80                   ; X (center it)
         sub     ax,cx
         shr     ax,1
         push    ax
         push        0                        ; Vio handle
         call    VioWrtCharStr           ; transfer to OS/2

         push        0                        ; pause for 3 seconds
         push        3000
         call    DosSleep                ; transfer to OS/2

         call    unpop                   ; release popup window

         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; exit program

 error   endp


 watch   proc    far                     ; keyboard thread, monitors
                                         ; for snapshot or exit hot keys

         mov     kpktlen,kpktlen-kbdpkt  ; max buffer length for read

                                         ; get keyboard data packet...
         push    ds                      ; monitor input buffer address
         push    offset DGROUP:kbdin
         push    0                       ; wait until data available
         push    ds
         push    offset DGROUP:kbdpkt    ; receives keyboard data packet
         push    ds
         push    offset DGROUP:kpktlen   ; contains/receives length
         call    DosMonRead              ; transfer to OS/2

         cmp     kbdpkt+2,0              ; is this extended code?
         jnz     watch1                  ; no, pass it on

         cmp        kbdpkt+3,exitkey        ; is it exit hot key?
         jz      watch2                  ; jump if exit key

         cmp        kbdpkt+3,snapkey        ; is it snapshot hot key?
         jnz     watch1                  ; no, jump

         cmp     word ptr kbdpkt+12,0    ; is it break packet?
         jnz     watch                   ; yes, ignore it

                                         ; snapshot hot key detected
                                         ; clear snapshot semaphore...
         push    ds                      ; semaphore handle
         push    offset DGROUP:snapsem
         call    DosSemClear             ; transfer to OS/2
         jmp        watch                        ; discard this hot key

 watch1:                                 ; not hot key, pass character
         push    ds                      ; monitor output buffer address
         push    offset DGROUP:kbdout
         push    ds                      ; keyboard data packet address
         push    offset DGROUP:kbdpkt
         push    kpktlen                 ; length of data packet
         call    DosMonWrite             ; transfer to OS/2

         jmp     watch                   ; get another packet

 watch2:                                 ; exit hot key detected...
         cmp     word ptr kbdpkt+12,0    ; is it break packet?
         jnz     watch                   ; yes, ignore it

                                         ; clear exit semaphore...
         push    ds                      ; semaphore handle
         push    offset DGROUP:exitsem
         call    DosSemClear             ; transfer to OS/2

         jmp     watch                   ; let thread 1 shut down

 watch   endp


 snap    proc    far                     ; This thread blocks on the
                                         ; snapshot semaphore, then
                                         ; dumps the screen contents
                                         ; to the file SNAPxx.IMG.

                                         ; open/create snapshot file
         push    ds                      ; address of filename
         push    offset DGROUP:fname
         push    ds                      ; variable to receive file handle
         push    offset DGROUP:fhandle
         push    ds                      ; variable to receive action taken
         push    offset DGROUP:action
         push    0                       ; initial file size
         push    0
         push    0                       ; normal file attribute
         push    12h                     ; create or replace file
         push    21h                     ; write access, deny write
         push    0                       ; DWORD reserved
         push    0
         call    DosOpen                 ; transfer to OS/2
         jerr    error,msg14,msg14_len   ; jump if can't create

 snap1:                                  ; write divider line
         push    fhandle                 ; file handle
         push    ds                      ; address of divider string
         push    offset DGROUP:divider
         push    divider_len             ; length of string
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; force disk update...
         push    fhandle                 ; file handle
         call    DosBufReset             ; transfer to OS/2

                                         ; wait on snapshot semaphore
         push    ds                      ; semaphore handle
         push    offset DGROUP:snapsem
         push    -1                      ; timeout = indefinite
         push    -1
         call    DosSemWait              ; transfer to OS/2

         mov        ax,3                        ; pop-up in transparent mode
         call    popup                   ; to read screen contents

                                         ; get screen dimensions...
         push    ds                      ; receives video mode info
         push    offset DGROUP:vioinfo
         push        0                        ; Vio handle
         call    VioGetMode              ; transfer to OS/2

         mov     bx,0                    ; BX := initial screen row

 snap2:                                  ; read line from screen...
         mov     ax,cols                 ; width to read
         mov     slen,ax
         push    ds                      ; address of screen buffer
         push    offset DGROUP:scrbuf
         push    ds                      ; contains/receives length
         push    offset DGROUP:slen
         push    bx                      ; screen row
         push    0                       ; screen column
         push        0                        ; Vio handle
         call    VioReadCharStr          ; transfer to OS/2

         push    ds                      ; scan backwards from end
         pop     es                      ; of line to find last
         mov        cx,slen                 ; nonblank character
         mov     di,offset DGROUP:scrbuf
         add     di,slen
         dec     di
         mov     al,20h
         std
         repe scasb
         cld
         jz      snap3                   ; if Z = True, line was empty
         inc     cx                      ; otherwise correct the length

 snap3:                                  ; write line to file...
         push    fhandle                 ; file handle
         push    ds                      ; address of data
         push    offset DGROUP:scrbuf
         push    cx                      ; clipped line length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; write newline (CR-LF)
         push    fhandle                 ; file handle
         push    ds
         push        offset DGROUP:newline        ; address of newline
         push        nl_len                        ; length of newline
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         inc     bx                      ; bump screen row counter
         cmp     bx,rows                 ; whole screen done yet?
         jne     snap2                   ; no, write another

         push    440                     ; reward user with some
         push    200                     ; audible feedback
         call    DosBeep                 ; transfer to OS/2

         call    unpop                   ; release the screen

                                         ; done with screen capture,
                                         ; reset snapshot semaphore
         push    ds                      ; semaphore handle
         push    offset DGROUP:snapsem
         call    DosSemSet               ; transfer to OS/2

         jmp     snap1                   ; go wait on semaphore

 snap    endp


 signon  proc    near                    ; announce installation,
                                         ; display help message

         mov     ax,1                    ; put up popup window
         call        popup                        ; mode = wait, nontransparen

         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_len                ; message length
         push    10                      ; Y
         push    (80-msg1_len)/2         ; X (center it)
         push        0                        ; Vio handle
         call    VioWrtCharStr           ; transfer to OS/2

         push    ds                      ; message address
         push    offset DGROUP:msg2
         push    msg2_len                ; message length
         push    13                      ; Y
         push    (80-msg2_len)/2         ; X (center it)
         push        0                        ; Vio handle
         call    VioWrtCharStr           ; transfer to OS/2

         push    ds                      ; message address
         push    offset DGROUP:msg3
         push    msg3_len                ; message length
         push    15                      ; Y
         push    (80-msg3_len)/2         ; X (center it)
         push        0                        ; Vio handle
         call    VioWrtCharStr           ; transfer to OS/2

         push        0                        ; pause for 4 seconds
         push        4000                        ; so user can read message
         call    DosSleep                ; transfer to OS/2

         call    unpop                   ; take down popup window
         ret                             ; back to caller

 signon  endp


 signoff proc        near                        ; announce deinstallation

         mov     ax,1                    ; put up popup window
         call        popup                        ; mode = wait, nontransparen

         push    ds                      ; message address
         push    offset DGROUP:msg4
         push    msg4_len                ; message length
         push    12                      ; Y
         push    (80-msg4_len)/2         ; X (center it)
         push    0                       ; VIO handle
         call    VioWrtCharStr

         push        0                        ; pause for 2 seconds
         push        2000                        ; so user can read message
         call    DosSleep                ; transfer to OS/2

         call    unpop                   ; take down popup window
         ret

 signoff endp


 popup   proc    near                    ; put up popup window
                                         ; AX = VioPopUp flags
                                         ; bit 0 = 0 no wait
                                         ;          1 wait for pop-up
                                         ; bit 1 = 0 nontransparent
                                         ;         1 transparent

         mov        pflags,ax                ; set pop-up mode

         push        ds                        ; address of popup flags
         push    offset DGROUP:pflags
         push        0                        ; Vio handle
         call    VioPopUp                ; transfer to OS/2
         ret                             ; back to caller

 popup   endp


 unpop   proc    near                    ; take down popup window

         push        0                        ; Vio handle
         call    VioEndPopUp             ; transfer to OS/2
         ret                             ; back to caller

 unpop   endp

 _TEXT   ends

         end     main



 SNAP.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH18\SNAP.C

 /*
         SNAP.C

         A sample OS/2 device monitor that captures the current
         display into the file SNAPxx.IMG, where xx is the
         session number.  SNAP works in character mode only and
         and may not be used in a PM window.  The following keys
         are defined as defaults:

         Alt-F10   hot key to capture a screen
         Ctrl-F10  hot key to deinstall SNAP.EXE

         Compile with:  C> cl /F 2000 snap.c

         Usage is:  C> snap

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>
 #include <string.h>

                                         /* hot key definitions */
 SNAPKEY  0x71                   /* Alt-F10 to capture screen */
 EXITKEY  0x67                   /* Ctrl-F10 to exit */

 STKSIZE  2048                   /* stack size for threads */

 WAIT     0                      /* parameters for DosMonRead */
 NOWAIT   1                      /* and DosMonWrite */

 API unsigned extern far pascal  /* API function prototypes */

 API DosBeep(unsigned, unsigned);
 API DosBufReset(unsigned);
 API DosClose(unsigned);
 API DosCloseSem(unsigned long far *);
 API DosCreateThread(void (far *)(), unsigned far *, void far *);
 API DosCreateSem(unsigned, unsigned long far *, char far *);
 API DosExecPgm(char far *, int, int, char far *, char far *,
                int far *, char far *);
 API DosExit(unsigned, unsigned);
 API DosGetInfoSeg(unsigned far *, unsigned far *);
 API DosMonClose(unsigned);
 API DosMonOpen(char far *, unsigned far *);
 API DosMonRead(void far *, unsigned, char far *, int far *);
 API DosMonReg(unsigned, void far *, void far *, int, unsigned);
 API DosMonWrite(void far *, char far *, int);
 API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
             unsigned, unsigned, unsigned, unsigned long);
 API DosOpenSem(unsigned long far *, char far *);
 API DosSemClear(unsigned long far *);
 API DosSemSet(unsigned long far *);
 API DosSemWait(unsigned long far *, unsigned long);
 API DosSetPrty(int, int, int, int);
 API DosSleep(unsigned long);
 API DosSuspendThread(unsigned);
 API DosWrite(unsigned, void far *, int, unsigned far *);
 API VioEndPopUp(unsigned);
 API VioGetMode(void far *, unsigned);
 API VioPopUp(unsigned far *, unsigned);
 API VioReadCharStr(char far *, int far *, int, int, unsigned);
 API VioWrtCharStr(char far *, int, int, int, unsigned);

 void signon(void);                      /* local function prototypes */
 void signoff(void);
 void popup(unsigned);
 void unpop(void);
 void far snap(void);
 void far watch(void);
 void errexit(char *);

                                         /* RAM semaphores */
 unsigned long exitsem = 0;                /* exit hot key semaphore */
 unsigned long snapsem = 0;              /* screen snapshot semaphore */

 char sname[20];                         /* system semaphore name */
 unsigned long shandle = 0;              /* system semaphore handle */

 char fname[20];                         /* snapshot filename */
 unsigned fhandle = 0;                   /* snapshot file handle */

 char kname[] = "KBD$";                  /* keyboard device name */
 unsigned khandle = 0;                   /* keyboard monitor handle */

 struct _monbuf {                        /* monitor input and */
     int len;                            /* output buffers */
     char buf[128];
     } kbdin  = { sizeof(kbdin.buf)  } ,
       kbdout = { sizeof(kbdout.buf) } ;

 struct _vioinfo {                       /* display mode info */
     int len;
     char type;
     char colors;
     int cols;
     int rows;
     } vioinfo;

 char msg1[] = "SNAP utility installed!";
 char msg2[] = "Alt-F10 to capture screen image into file SNAP.IMG,";
 char msg3[] = "Ctrl-F10 to shut down SNAP.";
 char msg4[] = "SNAP utility deactivated.";
 char msg5[] = "Error detected during SNAP installation:";

 main()
 {
     char obuff[80];                     /* object name buffer */
     int retcode[2];                     /* receives child info */

     unsigned gseg, lseg;                /* receives selectors */
     char far *ginfo;                        /* global info segment pointer */

     unsigned snapID, watchID;           /* receives thread IDs */
     char snapstk[STKSIZE];              /* snapshot thread stack */
     char watchstk[STKSIZE];             /* keyboard thread stack */

     DosGetInfoSeg(&gseg, &lseg);        /* get info segment selectors */
     (long) ginfo = (long) gseg << 16;   /* make far pointer */

                                         /* build semaphore and file names */
     sprintf(sname, "\\SEM\\SNAP%02d.LCK", ginfo[0x18]);
     sprintf(fname, "\\SNAP%02d.IMG",      ginfo[0x18]);

     if(DosOpenSem(&shandle, sname))     /* does \SEM\SNAPxx.LCK exist? */
     {
                                         /* no, we're parent SNAP */
                                         /* create system semaphore */
         if(DosCreateSem(1, &shandle, sname))
             errexit("Can't create SNAP system semaphore.");
         DosSemSet((unsigned long far *) shandle);

                                         /* start detached child SNAP */
         if(DosExecPgm(obuff, sizeof(obuff), 4, NULL, NULL, retcode, "snap.exe
             errexit("Can't start child copy of SNAP.");

                                         /* wait for child to load */
         DosSemWait((unsigned long far *) shandle, -1L);
         DosCloseSem((unsigned long far *) shandle);
     }
     else                                /* if SNAPxx.LCK exists, */
     {                                   /* we're the child SNAP */

                                         /* abort if already resident */
         if(! DosSemWait((unsigned long far *) shandle, 0L))
             errexit("SNAP is already loaded.");

         DosSemSet(&exitsem);            /* initialize exit and */
         DosSemSet(&snapsem);            /* snapshot semaphores */

         if(DosMonOpen(kname, &khandle)) /* open monitor connection */
             errexit("Can't open KBD$ monitor connection.");

                                         /* register at head of chain */
         if(DosMonReg(khandle, &kbdin, &kbdout, 1, ginfo[0x18]))
             errexit("Can't register as KBD$ monitor.");

                                         /* create keyboard thread */
         if(DosCreateThread(watch, &watchID, watchstk+STKSIZE))
             errexit("Can't create keyboard thread.");

         DosSetPrty(2, 3, 0, watchID);   /* promote keyboard thread */

                                         /* create snapshot thread */
         if(DosCreateThread(snap, &snapID, snapstk+STKSIZE))
             errexit("Can't create snapshot thread.");

         signon();                       /* announce installation */

                                         /* tell parent we're running */
         DosSemClear((unsigned long far *) shandle);

         DosSemWait(&exitsem, -1L);        /* wait for exit hot key */

         DosSuspendThread(snapID);       /* suspend snapshot thread */
         DosSuspendThread(watchID);      /* suspend keyboard thread */
         DosMonClose(khandle);           /* close monitor connection */
                                         /* close system semaphore */
         DosCloseSem((unsigned long far *) shandle);
         DosClose(fhandle);              /* close snapshot file */

         signoff();                        /* announce deinstallation */
     }

     DosExit(1, 0);                      /* final exit */
 }

 /*
     The 'watch' thread is responsible for monitoring the keyboard
     data stream.  It clears the 'snapsem' semaphore when the
     screen capture hot key is detected and clears the 'exitsem'
     semaphore when the deinstall hot key is detected.
 */
 void far watch(void)
 {
     char kbdpkt[128];                   /* monitor data packet */
     int kbdpktlen;                      /* data packet length */

     while(1)
     {
         kbdpktlen = sizeof(kbdpkt);     /* set buffer length */

                                         /* read monitor data */
         DosMonRead(&kbdin, WAIT, kbdpkt, &kbdpktlen);

                                         /* check for hot keys */
                                         /* ignore key breaks */
         if((kbdpkt[2] == 0) && (kbdpkt[3] == EXITKEY))
         {                                /* exit hot key detected */
             if(kbdpkt[12] == 0) DosSemClear(&exitsem);
         }
         else if((kbdpkt[2] == 0) && (kbdpkt[3] == SNAPKEY))
         {                                /* snapshot hot key detected */
             if(kbdpkt[12] == 0) DosSemClear(&snapsem);
         }                                /* not hot key, pass it through */
         else DosMonWrite(&kbdout, kbdpkt, kbdpktlen);
     }
 }

 /*
     The 'snap' thread blocks on the 'snapsem' semaphore until it
     is cleared by the 'watch' thread, then captures the current
     screen contents into the snapshot file.
 */
 void far snap(void)
 {
     int i;                              /* scratch variable */
     unsigned action;                    /* receives DosOpen action */
     unsigned wlen;                      /* receives DosWrite length */
     char divider[81];                   /* snapshot divider line */
     char scrbuf[80];                    /* receives screen data */
     int slen;                           /* contains buffer size */

     memset(divider, '-', 79);           /* initialize divider line */
     divider[79] = 0x0d;
     divider[80] = 0x0a;

                                         /* create/replace snapshot file */
     if(DosOpen(fname, &fhandle, &action, 0L, 0, 0x12, 0x21, 0L))
         errexit("Can't create snapshot file.");

     while(1)
     {                                   /* write divider line */
         DosWrite(fhandle, divider, sizeof(divider), &wlen);
         DosBufReset(fhandle);           /* force file update */

         DosSemWait(&snapsem, -1L);        /* wait for hot key */

         popup(3);                        /* pop-up in transparent mode */
         vioinfo.len = sizeof(vioinfo);  /* get screen dimensions */
         VioGetMode(&vioinfo, 0);

         for(i = 0; i < vioinfo.rows; i++)
         {
             slen = vioinfo.cols;        /* read line from screen */
             VioReadCharStr(scrbuf, &slen, i, 0, 0);

                                         /* discard trailing spaces */
             while((slen > 0) && (scrbuf[slen-1] == 0x20)) slen--;

                                         /* write line to file */
             DosWrite(fhandle, scrbuf, slen, &wlen);
             DosWrite(fhandle, "\x0d\x0a", 2, &wlen);
         }

         DosBeep(440, 200);              /* reward the user */
         unpop();                        /* release screen */
         DosSemSet(&snapsem);            /* reset snapshot semaphore */
     }
 }

 /*
     Display the installation and help messages in popup window.
 */
 void signon(void)
 {
     popup(1);                           /* acquire popup screen */
     VioWrtCharStr(msg1, sizeof(msg1), 10, ((80-sizeof(msg1))/2), 0);
     VioWrtCharStr(msg2, sizeof(msg2), 13, ((80-sizeof(msg2))/2), 0);
     VioWrtCharStr(msg3, sizeof(msg3), 15, ((80-sizeof(msg3))/2), 0);
     DosSleep(4000L);                        /* pause for 4 seconds */
     unpop();                            /* release popup screen */
 }

 /*
     Display exit message in popup window.
 */
 void signoff(void)
 {
     popup(1);                           /* acquire popup screen */
     VioWrtCharStr(msg4, sizeof(msg4), 12, ((80-sizeof(msg4))/2), 0);
     DosSleep(2000L);                        /* pause for 2 seconds */
     unpop();                            /* release popup screen */
 }

 /*
     Get popup screen, using wait/no-wait and
     transparent/nontransparent flags supplied by caller.
 */
 void popup(unsigned pflags)
 {
     VioPopUp(&pflags, 0);
 }

 /*
     Take down popup screen.
 */
 void unpop(void)
 {
     VioEndPopUp(0);
 }

 /*
     Common error exit routine.  Display error message on popup
     screen and terminate process.
 */
 void errexit(char *errmsg)
 {
     if(khandle != 0)                    /* close monitor handle */
         DosMonClose(khandle);           /* if monitor active */

     if(shandle != 0)                    /* clear and close the */
     {                                   /* SNAPxx.LCK semaphore */
         DosSemClear((unsigned long far *) shandle);
         DosCloseSem((unsigned long far *) shandle);
     }

     popup(1);                           /* get popup screen and */
                                         /* display error message */
     VioWrtCharStr(msg5, sizeof(msg5), 10, ((80-sizeof(msg5))/2), 0);
     VioWrtCharStr(errmsg, strlen(errmsg), 12, ((80-strlen(errmsg))/2), 0);
     DosSleep(3000L);                        /* let user read message */
     unpop();                            /* release popup screen */
     DosExit(1, 1);                        /* terminate, exitcode = 1 */
 }




 STAMPER.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH18\STAMPER.ASM

         title   STAMPER: a simple keyboard monitor
         page    55,132
         .286
         .sall
 ;
 ; STAMPER.EXE: a simple OS/2 monitor that inserts
 ; a date or time stamp into the keyboard data stream.
 ; Copyright (C) 1987 Ray Duncan
 ;
 ; Alt-D  is the hot-key for a date-stamp.
 ; Alt-T  is the hot-key for a time-stamp.
 ; Alt-X  causes the STAMPER.EXE monitor to exit.
 ;
 ; In order to keep this example program simple, it
 ; contains minimal error handling and does not check
 ; for previously loaded copies of itself in the same
 ; screen group etc.  For a more complete example, see
 ; the SNAP.ASM or SNAP.C program.
 ;
 ; Build with:   C>MASM STAMPER;
 ;               C>LINK STAMPER,,,OS2,STAMPER;
 ;
 ; Usage:        C>DETACH STAMPER

 stdin   equ     0                       ; standard device handles
 stdout  equ     1
 stderr  equ     2

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

                                         ; Hot-key definitions:
 datekey equ     20h                     ; Alt-D = insert date
 timekey equ     14h                     ; Alt-T = insert time
 exitkey equ     2dh                     ; Alt-X = exit program

         extrn   DosExit:far             ; references to OS/2 API
         extrn   DosGetInfoSeg:far
         extrn   DosMonClose:far
         extrn   DosMonOpen:far
         extrn   DosMonRead:far
         extrn   DosMonReg:far
         extrn   DosMonWrite:far
         extrn   DosSetPrty:far
         extrn   DosSleep:far
         extrn   DosWrite:far
         extrn   VioEndPopUp:far
         extrn   VioGetAnsi:far
         extrn   VioPopUp:far
         extrn   VioWrtCharStr:far

 jerr    macro   target                  ;; Macro to test return code
         local   zero                    ;; in AX and jump if non-zero.
         or      ax,ax                   ;; Uses JMP DISP16 to avoid
         jz      zero                    ;; branch out of range errors
         jmp     target
 zero:
         endm

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 kname   db      'KBD$',0                ; device name of keyboard
 khandle dw      0                       ; handle from DosMonOpen

 gseg    dw      ?                       ; global information segment
 lseg    dw      ?                       ; local information segment
 scrgrp  dw      ?                       ; foreground screen group
 ansi    dw      ?                       ; receives ANSI state
 popflag dw      1                       ; wait for PopUp window
 wlen    dw      ?                       ; receives length written

 dstr    db      'mm/dd/yy',0            ; strings used by time and
 tstr    db      'hh:mm',0               ; date formatting routines

 monin   dw      128,64 dup (0)          ; buffers for monitor
 monout  dw      128,64 dup (0)

 packet  db      128 dup (0)             ; buffer for kbd data packet
 pktlen  dw      ?                       ; contains max buffer length;
                                         ; receives actual data length

 msg1    db      cr,lf,'Start STAMPER with DETACH!',cr,lf
 msg1_len equ $-msg1

 msg2    db      'STAMPER utility installed'
 msg2_len equ $-msg2

 msg3    db      'Alt-D to insert date stamp,'
 msg3_len equ $-msg3

 msg4    db      'Alt-T to insert time stamp,'
 msg4_len equ $-msg4

 msg5    db      'Alt-X to shut down STAMPER.'
 msg5_len equ $-msg5

 msg6    db      'STAMPER utility deactivated.'
 msg6_len equ $-msg6

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,ss:DGROUP

 main    proc    far                     ; entry point from OS/2

                                         ; get info segment selectors...
         push    ds                      ; receives global selector
         push    offset DGROUP:gseg
         push    ds                      ; receives local selector
         push    offset DGROUP:lseg
         call    DosGetInfoSeg           ; transfer to OS/2
         jerr    main6                   ; give up if can't get selectors

                                         ; make sure we are detached...
         push    ds                      ; receives ANSI state flag
         push    offset DGROUP:ansi
         push    0                       ; VIO handle
         call    VioGetAnsi
         or      ax,ax                   ; call should fail, otherwise
         jnz     main1                   ; we are not detached.

                                         ; not detached, display error
                                         ; message and exit...
         push    stderr                  ; handle for standard error
         push    ds                      ; address of message
         push    offset DGROUP:msg1
         push    msg1_len                ; length of message
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2
         jmp     main6                   ; go exit with error code

 main1:  mov     es,gseg                 ; get foreground screen group
         mov     al,byte ptr es:[0018h]  ; from global info segment
         cbw
         mov     scrgrp,ax               ; and save it

                                         ; open monitor connection...
         push    ds                      ; address of device name KBD$
         push    offset DGROUP:kname
         push    ds                      ; receives monitor handle
         push    offset DGROUP:khandle
         call    DosMonOpen              ; transfer to OS/2
         jerr    main6                   ; give up if can't open it

                                         ; register as keyboard monitor...
         push    khandle                 ; handle from DosMonOpen
         push    ds                      ; addr of monitor input buffer
         push    offset DGROUP:monin
         push    ds                      ; addr of monitor output buffer
         push    offset DGROUP:monout
         push    1                       ; request front of list
         push    scrgrp                  ; index = screen group
         call    DosMonReg               ; transfer to OS/2
         jerr    main6                   ; give up if can't register

         call    signon                  ; else announce our presence

                                         ; promote main thread to time-
                                         ; critical priority...
         push    2                       ; scope = single thread this process
         push    3                       ; class = time-critical
         push    0                       ; level = 0 within class
         push    1                       ; thread ID
         call    DosSetPrty              ; transfer to OS/2

 main2:                                  ; monitor the keyboard character
                                         ; stream; when hot key detected,
                                         ; insert the appropriate date or
                                         ; time stamp, or exit.

         mov     pktlen,pktlen-packet    ; set max buffer length for read

                                         ; get next keyboard data packet
         push    ds                      ; address of monitor input buffer
         push    offset DGROUP:monin
         push    0                       ; wait until data available
         push    ds
         push    offset DGROUP:packet    ; buffer for keyboard data packet
         push    ds
         push    offset DGROUP:pktlen    ; receives length of data packet
         call    DosMonRead              ; transfer to OS/2

         cmp     byte ptr packet+2,0     ; is this extended code?
         jnz     main4                   ; no, just pass it on

         cmp     byte ptr packet+3,exitkey
         jz      main5                   ; jump if exit hot-key

         cmp     byte ptr packet+3,timekey
         jnz     main3                   ; jump if not time hot-key

         cmp     word ptr packet+12,0    ; discard break packets
         jnz     main2

         call    time                    ; insert the time stamp

         jmp     main2                   ; discard this key

 main3:                                  ; is it datestamp hot-key?
         cmp     byte ptr packet+3,datekey
         jnz     main4                   ; no, jump

         cmp     word ptr packet+12,0    ; discard break packets
         jnz     main2

         call    date                    ; insert the date stamp

         jmp     main2                   ; discard this key

 main4:                                  ; Not hot-key, pass packet on.
         push    ds                      ; address of monitor output buffer
         push    offset DGROUP:monout
         push    ds                      ; address of keyboard data packet
         push    offset DGROUP:packet
         push    pktlen                  ; length of data packet
         call    DosMonWrite             ; transfer to OS/2

         jmp     main2                   ; wait for another packet

 main5:                                  ; hotkey for de-install detected

         cmp     word ptr packet+12,0    ; make sure it's Break packet
         jz      main2                   ; if not just discard it

         push    khandle                 ; close the monitor connection
         call    DosMonClose             ; transfer to OS/2

         call    signoff                 ; announce STAMPER exit

         push    1                       ; terminate all threads
         push    0                       ; return success code
         call    DosExit                 ; final exit to OS/2

 main6:                                  ; common error exit point...
         push    1                       ; terminate all threads
         push    1                       ; return error code
         call    DosExit                 ; final exit to OS/2

 main    endp


 date    proc    near                    ; format and insert date stamp

         mov     es,gseg                 ; get selector for global
                                         ; read-only information segment

         mov     al,byte ptr es:[11h]    ; convert month to ASCII
         aam
         add     ax,'00'
         xchg    al,ah
         mov     word ptr dstr,ax

         mov     al,byte ptr es:[10h]    ; convert day to ASCII
         aam
         add     ax,'00'
         xchg    al,ah
         mov     word ptr dstr+3,ax

         mov     ax,word ptr es:[12h]    ; convert year to ASCII
         sub     ax,1900
         aam
         add     ax,'00'
         xchg    al,ah
         mov     word ptr dstr+6,ax

         mov     si,offset DGROUP:dstr   ; insert date stamp string
         call    stuff                   ; into keyboard data stream

         ret                             ; back to caller

 date    endp


 time    proc    near                    ; format and insert time stamp

         mov     es,gseg                 ; get selector for global
                                         ; read-only information segment

         mov     al,byte ptr es:[8]      ; convert hours to ASCII
         aam
         add     ax,'00'
         xchg    al,ah
         mov     word ptr tstr,ax

         mov     al,byte ptr es:[9]      ; convert minutes to ASCII
         aam
         add     ax,'00'
         xchg    al,ah
         mov     word ptr tstr+3,ax

         mov     si,offset DGROUP:tstr   ; insert time stamp string
         call    stuff                   ; into keyboard data stream

         ret                             ; back to caller

 time    endp


 stuff   proc    near                    ; insert string into keyboard
                                         ; data stream.  Call with
                                         ; SI = ASCIIZ string (null
                                         ;      is discarded)
                                         ; AL, SI destroyed.

 stuff1: lodsb                           ; get next character
         or      al,al                   ; is it null?
         jnz     stuff2                  ; no, use it
         ret                             ; yes, exit

 stuff2: mov     packet+2,al             ; place ASCII code into packet

                                         ; now send this character
                                         ; to the keyboard driver...
         push    ds                      ; monitor output buffer address
         push    offset DGROUP:monout
         push    ds                      ; keyboard data packet address
         push    offset DGROUP:packet
         push    pktlen                  ; data packet length
         call    DosMonWrite             ; transfer to OS/2

         jmp     stuff1                  ; do another character

 stuff   endp


 signon  proc    near                    ; use pop-up window to
                                         ; display help message

         push    ds                      ; put up PopUp window
         push    offset DGROUP:popflag   ; (wait until available)
         push    0                       ; VIO handle
         call    VioPopUp                ; transfer to OS/2

         mov     dx,offset DGROUP:msg2   ; message address
         mov     cx,msg2_len             ; length
         mov     ax,9                    ; Y coordinate
         call    center                  ; display it

         mov     dx,offset DGROUP:msg3   ; message address
         mov     cx,msg3_len             ; length
         mov     ax,13                   ; Y coordinate
         call    center                  ; display it

         mov     dx,offset DGROUP:msg4   ; message address
         mov     cx,msg4_len             ; length
         mov     ax,15                   ; Y coordinate
         call    center                  ; display it

         mov     dx,offset DGROUP:msg5   ; message address
         mov     cx,msg5_len             ; length
         mov     ax,17                   ; Y coordinate
         call    center                  ; display it

         push    0                       ; pause for 3 seconds
         push    3000                    ; (user must be quick reader!)
         call    DosSleep                ; transfer to OS/2

         push    0                       ; take down PopUp window
         call    VioEndPopUp             ; transfer to OS/2

         ret                             ; back to caller

 signon  endp


 signoff proc    near                    ; use pop-up window to
                                         ; announce exit

         push    ds                      ; put up PopUp window
         push    offset DGROUP:popflag   ; (wait until available)
         push    0                       ; VIO handle
         call    VioPopUp                ; transfer to OS/2

         mov     dx,offset DGROUP:msg6   ; address of signoff message
         mov     cx,msg6_len             ; length
         mov     ax,12                   ; Y coordinate
         call    center                  ; display it

         push    0                       ; pause for 1 second so
         push    1000                    ; user can read message
         call    DosSleep                ; transfer to OS/2

         push    0                       ; take down PopUp window
         call    VioEndPopUp             ; transfer to OS/2

         ret                             ; back to caller

 signoff endp


 center  proc    near                    ; center a message on screen
                                         ; call DX = msg offset,
                                         ; CX = length, AX = Y coordinate

         push    ds                      ; address of message
         push    dx
         push    cx                      ; length of message
         push    ax                      ; Y
         sub     cx,80                   ; X=((80-length)/2)
         neg     cx                      ; to center message
         shr     cx,1
         push    cx
         push    0                       ; VIO handle
         call    VioWrtCharStr           ; transfer to OS/2
         ret                             ; back to caller

 center  endp

 _TEXT   ends

         end     main


 TEMPLATE.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH17\TEMPLATE.ASM

         title        TEMPLATE -- Sample Device Driver
         page    55,132
         .286

 ;
 ; TEMPLATE.ASM
 ;
 ; A sample OS/2 character device driver.  The driver command code
 ; routines are stubs only and have no effect but to return a
 ; nonerror "done" status.
 ;
 ; Assemble with:  C> masm template.asm;
 ; Link with:  C> link template,template.sys,,os2,template
 ;
 ; To install the driver, add "DEVICE=TEMPLATE.SYS" to CONFIG.SYS
 ; and reboot.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 maxcmd  equ     26              ; maximum allowed command code

 stdin   equ     0               ; standard device handles
 stdout  equ     1
 stderr  equ     2

 cr      equ     0dh             ; ASCII carriage return
 lf        equ        0ah                ; ASCII linefeed

         extrn   DosWrite:far


 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

                                 ; device driver header...
 header  dd      -1              ; link to next device driver
         dw      8880h           ; device attribute word
         dw      Strat           ; Strategy entry point
         dw        0                ; IDC entry point
         db      'TEMPLATE'      ; logical device name
         db        8 dup (0)        ; reserved

 devhlp  dd      ?               ; DevHlp entry point

 wlen    dw      ?               ; receives DosWrite length

                                 ; Strategy routine dispatch table
                                 ; for request packet command code...
 dispch  dw      Init            ; 0  = initialize driver
         dw      MediaChk        ; 1  = media check
         dw      BuildBPB        ; 2  = build BIOS parameter block
         dw      Error           ; 3  = not used
         dw      Read            ; 4  = read from device
         dw        NdRead                ; 5  = nondestructive read
         dw      InpStat         ; 6  = return input status
         dw      InpFlush        ; 7  = flush device input buffers
         dw      Write           ; 8  = write to device
         dw      WriteVfy        ; 9  = write with verify
         dw      OutStat         ; 10 = return output status
         dw      OutFlush        ; 11 = flush output buffers
         dw      Error           ; 12 = not used
         dw      DevOpen         ; 13 = device open
         dw      DevClose        ; 14 = device close
         dw        RemMedia        ; 15 = removable media
         dw      GenIOCTL        ; 16 = generic IOCTL
         dw      ResetMed        ; 17 = reset media
         dw      GetLogDrv       ; 18 = get logical drive
         dw      SetLogDrv       ; 19 = set logical drive
         dw      DeInstall       ; 20 = de-install
         dw      Error           ; 21 = not used
         dw      PartFD          ; 22 = partitionable fixed disks
         dw      FDMap           ; 23 = get fixed disk unit map
         dw      Error           ; 24 = not used
         dw      Error           ; 25 = not used
         dw      Error           ; 26 = not used

 ident   db      cr,lf,lf
         db        'TEMPLATE Sample OS/2 Device Driver'
         db      cr,lf
 ident_len equ $-ident

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,es:NOTHING

 Strat   proc    far             ; Strategy entry point
                                 ; ES:BX = request packet address

         mov     di,es:[bx+2]    ; get command code from packet
         and     di,0ffh
         cmp     di,maxcmd       ; supported by this driver?
         jle     Strat1          ; jump if command code OK

         call    Error           ; bad command code
         jmp     Strat2

 Strat1: add     di,di           ; branch to command code routine
         call    word ptr [di+dispch]

 Strat2: mov     es:[bx+3],ax    ; status into request packet
         ret                     ; back to OS/2 kernel

 Strat   endp


 Intr    proc  far               ; driver Interrupt handler

         clc                     ; signal we owned interrupt
         ret                     ; return from interrupt

 Intr    endp


 ; Command code routines are called by the Strategy routine
 ; via the Dispatch table with ES:BX pointing to the request
 ; header.  Each routine should return ES:BX unchanged
 ; and AX = status to be placed in request packet:
 ; 0100H if 'done' and no error
 ; 0000H if thread should block pending interrupt
 ; 81xxH if 'done' and error detected (xx=error code)

 MediaChk proc   near            ; function 1 = media check

         mov     ax,0100h        ; return 'done' status
         ret

 MediaChk endp


 BuildBPB proc   near            ; function 2 = build BPB

         mov     ax,0100h        ; return 'done' status
         ret

 BuildBPB endp


 Read    proc    near            ; function 4 = read

         mov     ax,0100h        ; return 'done' status
         ret

 Read    endp


 NdRead        proc        near                ; function 5 = nondestructive r

         mov     ax,0100h        ; return 'done' status
         ret

 NdRead  endp


 InpStat proc    near            ; function 6 = input status

         mov     ax,0100h        ; return 'done' status
         ret

 InpStat endp


 InpFlush proc   near            ; function 7 = flush input buffers

         mov     ax,0100h        ; return 'done' status
         ret

 InpFlush endp


 Write   proc    near            ; function 8 = write

         mov     ax,0100h        ; return 'done' status
         ret

 Write   endp


 WriteVfy proc   near            ; function 9 = write with verify

         mov     ax,0100h        ; return 'done' status
         ret

 WriteVfy endp


 OutStat proc    near            ; function 10 = output status

         mov     ax,0100h        ; return 'done' status
         ret

 OutStat endp


 OutFlush proc   near            ; function 11 = flush output buffers

         mov     ax,0100h        ; return 'done' status
         ret

 OutFlush endp


 DevOpen proc    near            ; function 13 = device open

         mov     ax,0100h        ; return 'done' status
         ret

 DevOpen endp


 DevClose proc   near            ; function 14 = device close

         mov     ax,0100h        ; return 'done' status
         ret

 DevClose endp


 RemMedia proc   near            ; function 15 = removable media

         mov     ax,0100h        ; return 'done' status
         ret

 RemMedia endp


 GenIOCTL proc   near            ; function 16 = generic IOCTL

         mov     ax,0100h        ; return 'done' status
         ret

 GenIOCTL endp


 ResetMed proc   near            ; function 17 = reset media

         mov     ax,0100h        ; return 'done' status
         ret

 ResetMed endp


 GetLogDrv proc  near            ; function 18 = get logical drive

         mov     ax,0100h        ; return 'done' status
         ret

 GetLogDrv endp


 SetLogDrv proc  near            ; function 19 = set logical drive

         mov     ax,0100h        ; return 'done' status
         ret

 SetLogDrv endp


 DeInstall proc        near                ; function 20 = deinstall driver

         mov     ax,0100h        ; return 'done' status
         ret

 DeInstall endp


 PartFD  proc    near            ; function 22 = partitionable
                                 ;               fixed disk
         mov     ax,0100h        ; return 'done' status
         ret

 PartFD  endp


 FDMap   proc    near            ; function 23 = get fixed disk
                                 ;               logical unit map
         mov     ax,0100h        ; return 'done' status
         ret

 FDMap   endp


 Error   proc    near            ; bad command code

         mov     ax,8103h        ; error bit + 'done' status
                                 ; + "Unknown Command" code
         ret

 Error   endp


 Init    proc    near            ; function 0 = initialize

         mov     ax,es:[bx+14]   ; get DevHlp entry point
         mov     word ptr devhlp,ax
         mov     ax,es:[bx+16]
         mov     word ptr devhlp+2,ax

                                 ; set offsets to end of code
                                 ; and data segments
         mov     word ptr es:[bx+14],offset _TEXT:Init
         mov     word ptr es:[bx+16],offset DGROUP:ident

                                 ; display sign-on message...
         push    stdout          ; standard output handle
         push    ds              ; address of message
         push    offset DGROUP:ident
         push    ident_len       ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

         mov     ax,0100h        ; return 'done' status
         ret

 Init    endp

 _TEXT   ends

         end


 TESTCDLL.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH19\TESTCDLL.C

 /*
         TESTCDLL.C

         A simple test program for the CDLL.DLL dynlink library.

         Compile with:  C> cl testcdll.c /link cdll.lib

         Usage is:  C> testcdll

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>

 int extern far pascal MYFUNC(int, int);

 main()
 {
     int x;          /* temporary storage */

     printf("\nCalling CDLL.MYFUNC\n");

     x = MYFUNC(1, 2);

     printf("\nMYFUNC(1, 2) = %d\n", x);
 }


 TINYCMD.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\TINYCMD.ASM

         title        TINYCMD -- Simple Command Interpreter
         page    55,132
         .286

 ;
 ; TINYCMD.ASM
 ;
 ; A simple command interpreter for OS/2.
 ;
 ; Assemble with:  C> masm tinycmd.asm;
 ; Link with:  C> link tinycmd,,,os2,tinycmd
 ;
 ; Usage is:  C> tinycmd
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 cr      equ     0dh                     ; ASCII carriage return
 lf        equ        0ah                        ; ASCII linefeed
 tab     equ     09h                     ; ASCII tab
 blank   equ     20h                     ; ASCII blank code
 escape  equ     01bh                    ; ASCII escape code

 inpsize equ     80                      ; maximum input length

         extrn   DosExecPgm:far          ; OS/2 API functions
         extrn   DosExit:far
         extrn   DosGetVersion:far
         extrn   KbdStringIn:far
         extrn   VioWrtTTY:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 ; Intrinsic commands table: Each entry is an ASCIIZ string
 ; followed by the offset of the corresponding procedure.

 commands label  byte
         db        'CLS',0                 ; clear screen command
         dw      cls_cmd
         db      'VER',0                 ; display OS/2 version
         dw      ver_cmd
         db      'EXIT',0                ; exit from TINYCMD
         dw      exit_cmd
         db      0                       ; end of table

 input   db      (inpsize+2) dup (0)     ; keyboard input buffer
 ibinfo  dw      inpsize,0               ; input buffer info

 pbuff   db      (inpsize+2) dup (0)     ; program name moved here

 cinfo   dw      0                       ; child termination type
         dw      0                       ; child return code

 prompt  db      cr,lf,'>> '             ; TINYCMD's user prompt
 pr_len  equ     $-prompt

 delims  db      0,blank,tab,';,='       ; delimiters for first
 de_len  equ     $-delims                ; command line token

 pext    db      '.EXE',0                ; program file extension
 pe_len equ      $-pext

 wlen    dw      0                       ; receives bytes written
 verinfo db      0,0                     ; receives OS/2 version

 msg1    db      cr,lf,lf                ; error message
         db      'Bad command or filename'
         db      cr,lf
 msg1_len equ    $-msg1

 msg2    db      escape,'[2J'            ; ANSI escape sequence
 msg2_len equ    $-msg2                  ; to clear the screen

 msg3    db      cr,lf,lf                ; version number message
         db      'OS/2 version '
 msg3a   db      'nn.'
 msg3b   db      'nn'
         db      cr,lf
 msg3_len equ    $-msg3

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,es:DGROUP

 main    proc    far                     ; entry point from OS/2

         push    ds                      ; make DGROUP addressable
         pop     es                      ;  with ES too
         cld                             ; clear direction flag

 main1:                                  ; main interpreter loop
         call    gcmd                    ; get command from user

         call    intrinsic               ; check if intrinsic function
         jnc     main1                   ; yes, it was processed

         call        extrinsic                ; no, run EXE file, then
         jmp     main1                   ; get another command

 main    endp

 ; Try to match first command token against COMMANDS table.
 ; If match, run the routine, return Carry = False.
 ; If no match, return Carry = True.

 intrinsic proc  near

                                         ; point to command table
         mov     si,offset DGROUP:commands

 intr1:                                  ; try next table entry...
         cmp     byte ptr [si],0         ; end of entire table?
         je      intr6                   ; jump if table exhausted

         mov     di,offset DGROUP:input  ; point to user's command

 intr2:  mov     al,[si]                 ; next character from table

         or      al,al                   ; end of table entry?
         jz      intr3                   ; yes, jump

         cmp     al,[di]                 ; compare to input character
         jnz     intr4                   ; jump, found mismatch

         inc     si                      ; advance string pointers
         inc     di
         jmp     intr2

 intr3:  cmp     byte ptr [di],0         ; user's entry same length?
         jne     intr5                   ; no, not a match

         call    word ptr [si+1]         ; run the command routine

         clc                             ; return Carry = False
         ret                             ; as success signal

 intr4:  lodsb                           ; look for end of this
         or      al,al                   ; command string (null byte)
         jnz     intr4                   ; not end yet, loop

 intr5:  add     si,2                    ; skip over routine address
         jmp     intr1                   ; try to match next command

 intr6:  stc                             ; command not matched, exit
         ret                             ; with Carry = True

 intrinsic endp

 ; Append .EXE to first token and attempt to execute program,
 ; passing address of argument strings block and null pointer
 ; for environment (child process inherits TINYCMD's environment).

 extrinsic proc  near

         mov     si,offset DGROUP:input  ; copy first token of
         mov     di,offset DGROUP:pbuff  ; user command to pbuff

 extr1:  lodsb                           ; get next character
         or      al,al                   ; found null?
         jz      extr2                   ; yes, end of token
         stosb                           ; no, copy character
         jmp     extr1                   ; and get next char.

 extr2:  mov     si,offset DGROUP:pext   ; append .EXE extension
         mov     cx,pe_len               ; to token, forming
         rep movsb                       ; program filename

                                         ; try and run program...
         push    0                       ; object name buffer
         push    0
         push    0                       ; object name buffer length
         push        0                        ; asynchronous execution mode
         push        ds                        ; address of argument strings
         push    offset DGROUP:input
         push    0                       ; environment pointer
         push    0
         push    ds                      ; receives termination info
         push    offset DGROUP:cinfo
         push    ds                      ; address of program name
         push    offset DGROUP:pbuff
         call    DosExecPgm              ; transfer to OS/2
         or      ax,ax                   ; was function successful?
         jz      extr3                   ; yes, jump

                                         ; no, display error message...
         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_len                ; message length
         push    0                       ; default video handle
         call    VioWrtTTY               ; transfer to OS/2

 extr3:  ret                             ; back to caller

 extrinsic endp

 ; Get input from user, convert it to argument strings block
 ; by breaking out first token with null and appending double
 ; null to entire line, and convert first token to uppercase.

 gcmd    proc    near                    ; prompt user, get command

                                         ; display TINYCMD prompt...
         push    ds                      ; address of prompt
         push    offset DGROUP:prompt
         push    pr_len                  ; length of prompt
         push    0                       ; default video handle
         call    VioWrtTTY

         mov     ibinfo+2,0              ; disable buffer editing

                                         ; get entry from user...
         push    ds                      ; address of input buffer
         push    offset DGROUP:input
         push    ds                      ; input buffer info
         push    offset DGROUP:ibinfo
         push    0                       ; 0 = wait for input
         push    0                       ; default keyboard handle
         call    KbdStringIn

         mov     cx,inpsize              ; look for carriage return
         mov     al,cr                   ; if any...
         mov     di,offset DGROUP:input
         repnz scasb
         jnz     gcmd1
         dec     di
 gcmd1:        mov        word ptr [di],0         ; and replace with 2 nulls
         mov     si,offset DGROUP:input  ; break out first token...

 gcmd2:  lodsb                           ; compare next character
         mov     di,offset DGROUP:delims ; to delimiter table
         mov     cx,de_len
         repnz scasb
         jnz     gcmd2                   ; jump, no match
         mov     byte ptr [si-1],0       ; replace delimiter with 0

         mov        si,offset DGROUP:input        ; fold token to uppercase

 gcmd3:  cmp     byte ptr [si],0         ; end of token?
         jz      gcmd5                   ; found end, jump
         cmp     byte ptr [si],'a'
         jb      gcmd4                   ; jump if not 'a-z'
         cmp     byte ptr [si],'z'
         ja      gcmd4                   ; jump if not 'a-z'
         sub        byte ptr [si],'a'-'A'        ; convert to uppercase

 gcmd4:  inc     si                      ; go to next character
         jmp     gcmd3

 gcmd5:  ret                             ; back to caller

 gcmd    endp


 cls_cmd proc    near                    ; intrinsic CLS command

                                         ; send ANSI escape sequence
                                         ; to clear the screen...
         push    ds                      ; escape sequence address
         push    offset DGROUP:msg2
         push    msg2_len                ; escape sequence length
         push    0                       ; default video handle
         call    VioWrtTTY               ; transfer to OS/2
         ret                             ; back to caller

 cls_cmd endp


 ver_cmd proc    near                    ; intrinsic VER command

                                         ; get OS/2 version number...
         push    ds                      ; receives version info
         push    offset DGROUP:verinfo
         call    DosGetVersion           ; transfer to OS/2

                                         ; format the version...
         mov     al,verinfo              ; convert minor version
         aam                             ; number to ASCII
         add     ax,'00'
         xchg    al,ah
         mov     word ptr msg3b,ax       ; store ASCII characters

         mov     al,verinfo+1            ; convert major version
         aam                             ; number to ASCII
         add     ax,'00'
         xchg    al,ah
         mov     word ptr msg3a,ax       ; store ASCII characters

                                         ; display version number...
         push    ds                      ; message address
         push    offset DGROUP:msg3
         push    msg3_len                ; message length
         push    0                       ; default video handle
         call    VioWrtTTY               ; transfer to OS/2

         ret                             ; back to caller

 ver_cmd endp


 exit_cmd proc   near                    ; intrinsic EXIT Command

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push    0                       ; return code = 0
         call    DosExit                 ; transfer to OS/2

 exit_cmd endp

 _TEXT   ends

         end     main                    ; defines entry point


 TINYCMD.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH12\TINYCMD.C

 /*
         TINYCMD.C

         A simple command interpreter for OS/2.

         Compile with:  C> cl tinycmd.c

         Usage is:  C> tinycmd

         Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>
 #include <string.h>
 #include <memory.h>
                                         /* macro to return number of
                                            elements in a structure */
 #define DIM(x) (sizeof(x) / sizeof(x[0]))

 INPSIZE 80                      /* maximum input length */

 API unsigned extern far pascal        /* OS/2 API prototypes */

 API DosExecPgm(char far *, int, int, char far *, char far *,
                int far *, char far *);
 API DosGetVersion(char far *);

 unsigned intrinsic(char *);             /* local function prototypes */
 void extrinsic(char *);
 void cls_cmd(void);
 void ver_cmd(void);
 void exit_cmd(void);

 struct _commands {                      /* intrinsic commands table */
     char *name;                         /* command name */
     int  (*fxn)();                      /* command function */
     }
       commands[] = { "CLS",   cls_cmd,  /* built-in commands */
                      "VER",   ver_cmd,
                      "EXIT",  exit_cmd, };

 main(int argc, char *argv[])
 {
     char input[INPSIZE];                /* keyboard input buffer */

     while(1)                            /* main interpreter loop */
     {
         printf("\n>> ");                /* display prompt */
         memset(input, 0, INPSIZE);      /* initialize input buffer */
         gets(input);                    /* get keyboard entry */
         strtok(input, " ;,=\t");        /* break out first token */
         strupr(input);                        /* and fold to uppercase */

         if(! intrinsic(input))          /* if intrinsic command,
                                            run its function */
             extrinsic(input);                /* else assume EXE file */
     }
 }

 /*
     Try to match first token of command line with intrinsic
     command table.  If a match is found, run the associated
     routine and return true; otherwise, return false.
 */
 unsigned intrinsic(char *input)
 {
     int i;                              /* scratch variable */

     for(i=0; i < DIM(commands); i++)    /* search command table */
     {
         if(! strcmp(commands[i].name, input))
         {
             (*commands[i].fxn)();       /* if match, run routine */
             return(1);                  /* and return true */
         }
     }
     return(0);                          /* no match, return false */
 }

 /*
     Append .EXE to first token and attempt to execute program,
     passing address of argument strings block and null pointer
     for environment (child process inherits TINYCMD's environment).
 */
 void extrinsic(char *input)
 {
     char pbuff[INPSIZE];                /* buffer for program name */
     unsigned status;                    /* value from DosExecPgm */
     int cinfo[2];                       /* child process info */

     strcpy(pbuff, input);               /* copy first command token
                                            to local buffer */
     strcat(pbuff, ".EXE");              /* append .EXE to token */

                                         /* try to execute program */
     if(DosExecPgm(NULL,                 /* object name buffer pointer */
                   0,                        /* object name buffer length */
                   0,                    /* synchronous exec. mode */
                   input,                /* argument strings */
                   NULL,                 /* use parent's environment */
                   cinfo,                /* receives termination info */
                   pbuff))                /* program name pointer */

         printf("\nBad command or filename\n");
 }

 /*
     These are the subroutines for the intrinsic commands.
 */
 void cls_cmd(void)                      /* CLS command */
 {
     printf("\033[2J");                  /* ANSI escape sequence */
 }                                       /* to clear screen */

 void ver_cmd(void)                      /* VER command */
 {
     char verinfo[2];                    /* receives version info */

     DosGetVersion(verinfo);                /* get and display version */
     printf("\nOS/2 version %d.%02d\n", verinfo[0], verinfo[1]);
 }

 void exit_cmd(void)                     /* EXIT command */
 {
     exit(0);                            /* terminate TINYCMD */
 }


 TINYDISK.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH17\TINYDISK.ASM

         title        TINYDISK -- RAM Disk Device Driver
         page    55,132
         .286

 ;
 ; TINYDISK.ASM
 ;
 ; A sample OS/2 block device driver that installs a 64 KB RAM disk.
 ;
 ; Assemble with:  C> masm tinydisk.asm;
 ; Link with:  C> link tinydisk,tinydisk.sys,,os2,tinydisk
 ;
 ; To install the driver, add "DEVICE=TINYDISK.SYS" to CONFIG.SYS
 ; and reboot.
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 maxcmd  equ     26              ; maximum allowed command code

 secsize equ     512             ; bytes/sector, IBM compatible media

 stdin   equ     0               ; standard device handles
 stdout  equ     1
 stderr  equ     2

 cr      equ     0dh             ; ASCII carriage return
 lf        equ        0ah                ; ASCII linefeed

 PhysToVirt   equ 15h            ; DevHlp services
 UnPhysToVirt equ 32h
 VerifyAccess equ 27h
 AllocPhys    equ 18h

         extrn   DosWrite:far


 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

                                 ; device driver header...
 header  dd      -1              ; link to next device driver
         dw      0080h           ; device attribute word
                                 ; bits 7-9 = driver level
         dw      Strat           ; Strategy entry point
         dw        0                ; reserved
         db      0               ; units (set by OS/2)
         db        15 dup (0)        ; reserved

 devhlp  dd      ?               ; DevHlp entry point

 wlen    dw      ?               ; receives DosWrite length

 dbase   dd      ?               ; 32-bit physical address,
                                 ; base of RAM disk storage

 xfrsec  dw      0               ; current sector for transfer
 xfrcnt  dw      0               ; sectors successfully transferred
 xfrreq  dw      0               ; number of sectors requested
 xfraddr dd      0               ; working address for transfer

 array   dw      bpb             ; array of pointers to BPB
                                 ; for each supported unit

 bootrec equ     $               ; logical sector 0 boot record
         jmp     $               ; JMP at start of boot sector,
         nop                        ; this field must be 3 bytes
         db        'IBM 10.1'        ; OEM identity field
                                 ; ---BIOS Parameter Block-----
 bpb     dw      secsize         ; 00H bytes per sector
         db      1               ; 02H sectors per cluster
         dw      1               ; 03H reserved sectors
         db      1               ; 05H number of FATs
         dw      64              ; 06H root directory entries
         dw      128             ; 08H total sectors
         db      0f8h            ; 0AH media descriptor
         dw      1               ; 0BH sectors per FAT
         dw      1               ; 0DH sectors per track
         dw      1               ; 0FH number of heads
         dd      0               ; 11H hidden sectors
         dd      0               ; 15H large total sectors (if
                                 ;     word at offset 08H = 0)
         db      6 dup (0)       ; 19H reserved
                                 ; ---End of BPB, 31 bytes-----
 bootrec_len equ $-bootrec       ; length of boot sector data
                                 ; additional words needed by
                                 ; Generic IOCTL Cat 8 Function 63H
                                 ; Get Device Parameters call
         dw      0               ; number of cylinders
         db      7               ; device type = unknown
         dw      1               ; device attribute word
 gdprec_len equ $-bpb            ; length of Generic IOCTL buffer

                                 ; Strategy routine dispatch table
                                 ; for request packet command code...
 dispch  dw      Init            ; 0  = initialize driver
         dw      MediaChk        ; 1  = media check on block device
         dw      BuildBPB        ; 2  = build BIOS parameter block
         dw      Error           ; 3  = reserved
         dw      Read            ; 4  = read (input) from device
         dw        Error                ; 5  = nondestructive read
         dw      Error           ; 6  = return input status
         dw      Error           ; 7  = flush device input buffers
         dw      Write           ; 8  = write (output) to device
         dw      Write           ; 9  = write with verify
         dw      Error           ; 10 = return output status
         dw      Error           ; 11 = flush output buffers
         dw      Error           ; 12 = reserved
         dw      Error           ; 13 = device open
         dw      Error           ; 14 = device close
         dw        Error                ; 15 = removable media
         dw      GenIOCTL        ; 16 = generic IOCTL
         dw      Error           ; 17 = reset media
         dw      GSLogDrv        ; 18 = get logical drive
         dw      GSLogDrv        ; 19 = set logical drive
         dw        Error                ; 20 = deinstall
         dw      Error           ; 21 = reserved
         dw      Error           ; 22 = partitionable fixed disks
         dw      Error           ; 23 = get fixed disk unit map
         dw      Error           ; 24 = reserved
         dw      Error           ; 25 = reserved
         dw      Error           ; 26 = reserved

                                 ; start of data discarded
                                 ; after initialization

 ident        db        cr,lf,lf        ; successful installation message...
         db        'TINYDISK 64 KB RAM disk for OS/2'
         db      cr,lf
         db        'RAM disk will be drive '
 drive   db      'X:'
         db      cr,lf
 ident_len equ $-ident

 abort   db      cr,lf           ; aborted installation message
         db        'TINYDISK installation aborted'
         db      cr,lf
 abort_len equ $-abort

 volname db        'TINYDISK   '        ; volume label for RAM disk
         db      08h             ; attribute byte
         db      10 dup (0)      ; reserved area
         dw      0               ; time = 00:00
         dw      1021h           ; date = January 1, 1988
         db      6 dup (0)       ; reserved area
 volname_len equ $-volname

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP,es:NOTHING

 Strat   proc    far             ; Strategy entry point
                                 ; ES:BX = request packet

         mov     di,es:[bx+2]    ; get command code from packet
         and     di,0ffh
         cmp     di,maxcmd       ; supported by this driver?
         jle     Strat1          ; jump if command code OK

         call    Error           ; bad command code
         jmp     Strat2

 Strat1: add     di,di           ; go to command code routine
         call    word ptr [di+dispch]

 Strat2:                         ; return with AX = status
         mov     es:[bx+3],ax    ; put status in request packet
         ret                     ; return to OS/2 kernel

 Strat   endp


 ; Command code routines are called by the Strategy routine
 ; via the Dispatch table with ES:BX pointing to the request
 ; header.  Each routine should return ES:BX unchanged,
 ; and AX = status to be placed in request packet:
 ; 0100H if 'done' and no error
 ; 0000H if thread should block pending interrupt
 ; 81xxH if 'done' and error detected (xx = error code)


 MediaChk proc   near            ; function 1 = media check

                                 ; return 'not changed' code
         mov     byte ptr es:[bx+0eh],1

         mov     ax,0100h        ; return 'done' status
         ret

 MediaChk endp


 BuildBPB proc   near            ; function 2 = build BPB

                                 ; put BPB address into
                                 ; request packet
         mov     word ptr es:[bx+12h],offset DGROUP:bpb
         mov     word ptr es:[bx+14h],ds

         mov     ax,0100h        ; return 'done' status
         ret

 BuildBPB endp


 Read    proc    near            ; function 4 = read

         push    es              ; save request packet address
         push    bx

         call    setup           ; set up transfer variables

 Read1:  mov     ax,xfrcnt       ; done with all sectors yet?
         cmp     ax,xfrreq
         je      read2           ; jump if transfer completed

         mov     ax,ds           ; set ES = DGROUP
         mov     es,ax

         mov     ax,xfrsec       ; get sector number
         call    MapDS           ; and map it to DS:SI
                                 ; (may force mode switch)

         push    es              ; save DGROUP selector

                                 ; convert destination physical
                                 ; address to virtual address...
         mov     bx,word ptr es:xfraddr
         mov     ax,word ptr es:xfraddr+2
         mov     cx,secsize      ; segment length
         mov     dh,1            ; leave result in ES:DI
         mov     dl,PhysToVirt   ; function number
         call    es:devhlp       ; transfer to kernel

         mov     cx,secsize      ; transfer logical sector from
         cld                        ; RAM disk to requestor
         rep movsb

         pop     ds              ; restore DGROUP addressing

         sti                        ; PhysToVirt may mask interrupts

         inc     xfrsec          ; advance sector number

                                 ; advance transfer address
         add     word ptr xfraddr,secsize
         adc     word ptr xfraddr+2,0

         inc     xfrcnt          ; count sectors transferred
         jmp     read1

 Read2:                          ; all sectors transferred

         mov     dl,UnPhysToVirt ; function number
         call    devhlp          ; transfer to kernel

         pop     bx              ; restore request packet address
         pop     es

         mov     ax,xfrcnt       ; put actual transfer count
         mov     es:[bx+12h],ax  ; into request packet

         mov     ax,0100h        ; return 'done' status
         ret

 Read    endp


 Write   proc    near            ; functions 8,9 = write

         push    es              ; save request packet address
         push    bx

         call    setup           ; set up transfer variables

 Write1: mov     ax,xfrcnt       ; done with all sectors yet?
         cmp     ax,xfrreq
         je      write2          ; jump if transfer completed

         mov     ax,xfrsec       ; get sector number
         call    MapES           ; map it to ES:DI
                                 ; (may force mode switch)

         push    ds              ; save DGROUP selector

                                 ; convert source physical
                                 ; address to virtual address...
         mov     bx,word ptr xfraddr
         mov     ax,word ptr xfraddr+2
         mov     cx,secsize      ; segment length
         mov     dh,0            ; leave result in DS:SI
         mov     dl,PhysToVirt   ; function number
         call    devhlp          ; transfer to kernel

         mov     cx,secsize      ; transfer logical sector from
         cld                        ; requestor to RAM disk
         rep movsb

         pop     ds              ; restore DGROUP addressing
         sti                     ; PhysToVirt might have masked
         inc     xfrsec          ; advance sector number

                                 ; advance transfer address
         add     word ptr xfraddr,secsize
         adc     word ptr xfraddr+2,0

         inc     xfrcnt          ; count sectors transferred
         jmp     write1

 Write2:                         ; all sectors transferred

         mov     dl,UnPhysToVirt ; function number
         call    devhlp          ; transfer to kernel

         pop     bx              ; restore request packet address
         pop     es

         mov     ax,xfrcnt       ; put actual transfer count
         mov     es:[bx+12h],ax  ; into request packet

         mov     ax,0100h        ; return 'done' status
         ret

 Write   endp


 GenIOCTL proc   near            ; function 16 = generic IOCTL

         push    es              ; save request packet address
         push    bx

         mov     ax,8103h        ; assume unknown command

                                 ; Get Device Parameters call?
         cmp     byte ptr es:[bx+0dh],8
         jne        Gen9                ; no, set 'done' + 'error'
         cmp     byte ptr es:[bx+0eh],63h
         jne        Gen9                ; no, set 'done' + 'error'

                                 ; verify user's access
         mov     ax,es:[bx+15h]  ; selector
         mov     di,es:[bx+13h]  ; offset
         mov     cx,gdprec_len   ; length to be written
         mov     dh,1            ; need read/write access
         mov     dl,VerifyAccess ; function number
         call    devhlp          ; transfer to kernel

         mov     ax,810ch        ; if no access, exit with
         jc        Gen9                ; 'done' + general failure

                                 ; get destination address
         les     di,dword ptr es:[bx+13h]
                                 ; copy device info to caller
         mov     si,offset DGROUP:bpb
         mov     cx,gdprec_len   ; length of parameter block
         cld
         rep movsb

         mov     ax,0100h        ; set 'done' status

 Gen9:   pop     bx              ; restore request packet address
         pop     es
         ret                     ; and return with status

 GenIOCTL endp


 GSLogDrv proc   near            ; function 18, 19 = get
                                 ; or set logical drive

                                 ; return code indicating there
                                 ; are no logical drive aliases
         mov     byte ptr es:[bx+1],0

         mov     ax,0100h        ; return 'done' status
         ret

 GSLogDrv endp


 Error   proc    near            ; bad command code

         mov     ax,8103h        ; error bit + 'done' status
         ret                     ; + "Unknown Command" code

 Error   endp


 MapES   proc    near            ; map sector number to
                                 ; virtual memory address
                                 ; call with AX = logical sector
                                 ; return ES:DI = memory address
                                 ;        and Carry clear
                                 ; or     Carry set if error

         mov     di,secsize      ; bytes per sector
         mul     di              ; * logical sector number
         xchg    ax,dx           ; AX:BX := 32-bit physical
         mov     bx,dx           ; sector address
         add     bx,word ptr dbase
         adc     ax,word ptr dbase+2

         mov     cx,secsize
         mov     dh,1            ; result to ES:DI
         mov     dl,PhysToVirt   ; function number
         call    devhlp          ; transfer to kernel

         ret                     ; return ES:DI = sector address

 MapES   endp


 MapDS   proc    near            ; map sector number to
                                 ; virtual memory address
                                 ; call with AX = logical sector
                                 ; return DS:SI = memory address
                                 ;        and Carry clear
                                 ; or     Carry set if error

         mov     si,secsize      ; bytes per sector
         mul     si              ; * logical sector number
         xchg    ax,dx           ; AX:BX := 32-bit physical
         mov     bx,dx           ; sector address
         add     bx,word ptr dbase
         adc     ax,word ptr dbase+2

         mov     cx,secsize
         mov     dh,0            ; result to DS:SI
         mov     dl,PhysToVirt   ; function number
         call    devhlp          ; transfer to kernel

         ret                     ; return DS:SI = sector address

 MapDS   endp


 Setup   proc    near            ; set up for read/write
                                 ; ES:BX = request packet
                                 ; extracts address, start, count

         mov     ax,es:[bx+14h]  ; starting sector number
         mov     xfrsec,ax

         mov     ax,es:[bx+12h]  ; sectors requested
         mov     xfrreq,ax

         mov     ax,es:[bx+0eh]  ; requestor's buffer address
         mov     word ptr xfraddr,ax
         mov     ax,es:[bx+10h]
         mov     word ptr xfraddr+2,ax

         mov     xfrcnt,0        ; initialize sectors
                                 ; transferred counter
         ret

 Setup   endp

                                 ; start of code discarded
                                 ; after initialization

 Init    proc    near            ; function 0 = initialize

         mov     ax,es:[bx+0eh]  ; get DevHlp entry point
         mov     word ptr devhlp,ax
         mov     ax,es:[bx+10h]
         mov     word ptr devhlp+2,ax

         mov     al,es:[bx+16h]  ; unit code for this drive
         add     al,'A'          ; convert to ASCII and place
         mov        drive,al        ; in output string

                                 ; display sign-on message...
         push    stdout          ; handle for standard output
         push    ds              ; address of message
         push    offset DGROUP:ident
         push    ident_len       ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite

                                 ; set offsets to end of code
                                 ; and data segments
         mov     word ptr es:[bx+0eh],offset _TEXT:Init
         mov     word ptr es:[bx+10h],offset DGROUP:ident

                                 ; logical units supported
         mov     byte ptr es:[bx+0dh],1

                                 ; pointer to BPB array
         mov     word ptr es:[bx+12h],offset DGROUP:array
         mov     word ptr es:[bx+14h],ds

         push    es              ; save request packet address
         push    bx

                                 ; allocate RAM disk storage
         mov     bx,0            ; AX:BX = size of allocated
         mov     ax,1            ; block in bytes
         mov     dh,0            ; request block above 1 MB
         mov     dl,AllocPhys    ; function number
         call    devhlp          ; transfer to kernel
         jc      Init9           ; jump if allocation failed

                                 ; save physical address
                                 ; of allocated block
         mov     word ptr dbase,bx
         mov     word ptr dbase+2,ax

         call        format                ; format the RAM disk
         jc      Init9           ; jump if format failed

         pop     bx              ; restore request packet address
         pop     es

         mov     ax,0100h        ; return 'done' status
         ret

 Init9:                          ; abort driver installation

         pop     bx              ; restore request packet address
         pop     es

                                 ; display installation aborted...
         push    stdout          ; handle for standard output
         push    ds              ; address of message
         push    offset DGROUP:abort
         push    abort_len       ; length of message
         push    ds              ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite        ; transfer to OS/2

                                 ; set zero units
         mov     byte ptr es:[bx+0dh],0

                                 ; set lengths of code and
                                 ; data segments
         mov     word ptr es:[bx+0eh],0
         mov     word ptr es:[bx+10h],0

         mov     ax,810ch        ; return error + done +
                                 ; general failure
         ret

 Init    endp


 Format        proc        near                ; format the RAM disk area

                                 ; calculate length of
                                 ; RAM disk control areas
         mov     al,byte ptr bpb+5
         cbw                     ; number of FATs
         mov     cx,bpb+0bh      ; * sectors per FAT
         mul     cx
         mov        cx,bpb+6        ; entries in root directory
         shr        cx,4                ; divided by 16 for sectors
         add     ax,cx           ; (FAT + dir sectors
         add     ax,bpb+3        ; + reserved sectors)
         mov     cx,secsize      ; * bytes/sector
         mul     cx              ; = total length
         mov     cx,ax

                                 ; convert RAM disk base to
                                 ; virtual address
         mov     bx,word ptr dbase
         mov     ax,word ptr dbase+2
         mov     dh,1            ; leave result in ES:DI
         mov     dl,PhysToVirt   ; function number
         call    devhlp          ; transfer to kernel
         jc      Format9         ; jump if error

         xor     ax,ax           ; now zero control areas
         cld                     ; (assume CX still = length)
         rep stosb

         mov     ax,0            ; get address of logical
         call    MapES           ; sector zero
         jc      Format9         ; jump if error
         mov     si,offset DGROUP:bootrec
         mov     cx,bootrec_len  ; copy boot record
         rep movsb               ; to logical sector zero

         mov     ax,word ptr bpb+3
         call    MapES           ; get address of FAT sector
         jc      Format9         ; jump if error
         mov     al,byte ptr bpb+0ah
         mov     es:[di],al      ; media ID byte into FAT
         mov     word ptr es:[di+1],-1

         mov     ax,word ptr bpb+3
         add     ax,word ptr bpb+0bh
         call    MapES           ; get address of directory
         jc      Format9         ; jump if error
         mov     si,offset DGROUP:volname
         mov     cx,volname_len
         rep movsb               ; copy volume label to it

         mov     dl,UnPhysToVirt ; function number
         call    devhlp          ; transfer to kernel

         clc                     ; signal format successful

 Format9:                        ; return with Carry = 0
         ret                     ; if success, 1 if error

 Format  endp

 _TEXT   ends

         end


 TOUCH.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH15\TOUCH.C

 /*
     TOUCH.C     Force time and date stamps on file(s)

     Compile:    C> CL touch.c

     Usage:      C> TOUCH pathname [ pathname... ]

     Pathnames may include wildcard characters

     Copyright (C) 1988 Ray Duncan
 */

 #include <stdio.h>
 #include <string.h>

 #define API unsigned extern far pascal

 API DosClose(unsigned);                 /* OS/2 function prototypes */
 API DosFindClose(unsigned);
 API DosFindFirst(char far *, unsigned far *, unsigned, char far *,
                  unsigned, int far *, unsigned long);
 API DosFindNext(unsigned, char far *, unsigned, unsigned far *);
 API DosGetDateTime(void far *);
 API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long,
             unsigned, unsigned, unsigned, unsigned long);
 API DosSetFileInfo(unsigned, int, void far *, int);

 READ_ONLY 0x01                  /* file attribute bits */
 #define HIDDEN    0x02
 #define SYSTEM    0x04
 #define DIRECTORY 0x10
 #define ARCHIVE   0x20

 ATTR      0                     /* attributes to use
                                            during file search */

 struct _finfo {                         /* used by DosSetFileInfo */
                     unsigned cdate;
                     unsigned ctime;
                     unsigned adate;
                     unsigned atime;
                     unsigned wdate;
                     unsigned wtime;
               }     finfo;

 struct _dinfo {                         /* used by DosGetDateTime */
                     char hour;
                     char min;
                     char sec;
                     char csec;
                     char day;
                     char mon;
                     int  year;
                     int  zone;
                     char dow;
               }     dinfo;


 main(int argc, char *argv[])
 {
     int i;

     if(argc < 2)
     {
         printf("\ntouch: missing filename\n");
         exit(1);
     }

     DosGetDateTime(&dinfo);             /* get current date&time */

                                         /* set up date & time of
                                            last write in directory
                                            format for DosSetFileInfo */
     finfo.wdate = ((dinfo.year-1980)<<9) + (dinfo.mon<<5) + dinfo.day;
     finfo.wtime = (dinfo.hour<<11) + (dinfo.min<<5);

                                         /* file creation and last
                                            access fields useless
                                            in FAT file systems */
     finfo.cdate = finfo.ctime = finfo.adate = finfo.atime = 0;

     for(i = 1; i < argc; i++)           /* process all pathnames */
         findfiles(argv[i]);             /* in the command line */

     puts("");                           /* final blank line */
 }

 /*
     Search for all files matching a command line argument
 */
 findfiles(char *cname)
 {
     char resbuf[36];                    /* receives search results */
     unsigned status;                    /* receives function status */
     unsigned handle = -1;               /* directory search handle */
     unsigned attr = 0;                  /* attribute for search */
     int matches = 1;                    /* no. of matches requested/found */

                                         /* is there any match? */
     if(DosFindFirst(cname, &handle, ATTR, resbuf, 36, &matches, 0L) == 0)
     {
         stampfile(cname, &resbuf[23]);  /* initial match found */

                                         /* any additional matches? */
         while(DosFindNext(handle, resbuf, 36, &matches) == 0)
             stampfile(cname, &resbuf[23]);
     }
     else printf("\nno matches:  %s", strlwr(cname));
     DosFindClose(handle);               /* release search handle */
 }


 /*
     Set the time and date stamp on a file
 */

 stampfile(char *cname, char *sname)
 {
     unsigned status, handle, action;    /* scratch variables */
     char *p;                            /* scratch pointer */
     char qbuff[80];                     /* qualified filename */

     memset(qbuff,0,80);                 /* initialize buffer */

     p = strrchr(cname, '\\');           /* look for backslash */

     if(p != NULL)                       /* any path present? */
         memcpy(qbuff,cname,p-cname+1);  /* yes, copy it */
     else                                /* no, is drive present? */
         if((strlen(cname) >= 2) && (cname[1] == ':'))
         {
             qbuff[0] = cname[0];        /* yes, copy drive */
             qbuff[1] = cname[1];
         }
     strcat(qbuff,sname);                /* add filename from search
                                            to drive +/or path */

                                         /* try to open the file */
     if(DosOpen(qbuff, &handle, &action, 0L, 0, 1, 0x22, 0L))
         printf("\ncan't open:  %s", strlwr(qbuff));
     else
     {                                   /* set new time & date stamp */
         if(DosSetFileInfo(handle, 1, &finfo, sizeof(finfo)))
             printf("\ncan't touch: %s", strlwr(qbuff));
         else                            /* audit touched files */
             printf("\ntouched:     %s", strlwr(qbuff));

         DosClose(handle);               /* release file handle */
     }
 }



 TRYQFN.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\TRYQFN.ASM

         title   TRYQFN -- QFN demo
         page    55,132
         .286

 ; TRYQFN.ASM --- Interactive demo of QFN routine (OS/2 version)
 ; Copyright (C) 1988 Ray Duncan
 ;
 ; Build:        MASM TRYQFN;
 ;                MASM QFN;
 ;                LINK TRYQFN+QFN,,,OS2,TRYQFN.DEF;
 ;
 ; Usage:        TRYQFN

 cr      equ     0dh             ; ASCII carriage return
 lf      equ     0ah             ; ASCII line feed

 stdin   equ     0               ; standard input handle
 stdout  equ     1               ; standard output handle
 stderr  equ     2               ; standard error handle

         extrn        DosRead:far        ; OS/2 API functions
         extrn        DosWrite:far
         extrn        DosExit:far

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 ibuff   db      80 dup (0)      ; input buffer
 ibuff_len equ        $-ibuff

 rlen        dw        0                ; length from DosRead
 wlen        dw        0                ; length from DosWrite

 msg1    db      cr,lf,lf
         db      'Enter filename:        '
 msg1_len equ $-msg1

 msg2    db      cr,lf
         db      'The full pathname is:  '
 msg2_len equ $-msg2

 msg3    db      cr,lf
         db      'Bad filename!'
 msg3_len equ $-msg3

 _DATA   ends


 _TEXT   segment word public 'CODE'

         extrn   qfn:near

         assume  cs:_TEXT,ds:DGROUP

 main    proc    near

         push        ds                ; set ES = DGROUP too
         pop        es

 main1:                          ; display prompt...
         push        stdout                ; standard output handle
         push        ds                ; address of message
         push        offset DGROUP:msg1
         push        msg1_len        ; length of message
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

                                 ; get a filename...
         push        stdin                ; standard input handle
         push        ds                ; input buffer address
         push        offset DGROUP:ibuff
         push        ibuff_len        ; length of input buffer
         push        ds                ; receives actual length
         push        offset DGROUP:rlen
         call        DosRead                ; transfer to OS/2

         mov        ax,rlen                ; anything entered?
         cmp     ax,2                ; anything entered?
         je      main3           ; empty line, exit

                                 ; call QFN routine to
                                 ; validate and qualify
                                 ; the filename...

         sub     ax,2                 ; AX=length (remove CR-LF)
                                 ; DS:SI = addr of filename
         mov     si,offset DGROUP:ibuff
         call    qfn             ; go qualify filename
         jc      main2           ; jump if bad filename
         mov        cx,ax                ; save length

                                 ; first display title...
         push        stdout                ; standard output handle
         push        ds                ; message address
         push        offset DGROUP:msg2
         push        msg2_len             ; message length
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

                                 ; display qualified filename
         push        stdout                ; standard output handle
         push        ds                ; address of filename
         push        si
         push        cx                ; length of filename
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

         jmp     main1           ; get another filename

 main2:                          ; display error message...
         push        stdout                ; standard output handle
         push        ds                ; message address
         push        offset DGROUP:msg3
         push        msg3_len        ; message length
         push        ds                ; receives bytes written
         push        offset DGROUP:wlen
         call        DosWrite        ; transfer to OS/2

         jmp     main1           ; get another filename

 main3:  push        1                ; terminate with
         push        0                ; return code = 0
         call        DosExit                ; transfer to OS/2

 main    endp

 _TEXT   ends

         end     main



 TRYQFN.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\TRYQFN.C

 /*
     TRYQFN.C    Interactive demo of use of QFN.C
                 module to qualify filenames.
     Copyright (C) 1988 Ray Duncan

     Build:  CL TRYQFN.C QFN.C

     Usage:  TRYQFN

 */

 #include <stdio.h>

 extern char *qfn(char *);

 main(int argc, char *argv[])
 {
     char buffer[80];                /* buffer for user input */

     char *p;                        /* receives ptr from qfn */

     while(1)                        /* do until empty line */
     {
                                     /* prompt user for filename */
         printf("\nEnter filename:        ");

         gets(buffer);               /* read in filename */

         if(buffer[0] == 0) break;   /* exit if no entry */

         p = qfn(buffer);            /* else qualify filename */

         if(p != NULL)               /* display filename or
                                        error message */

             printf("\nThe full pathname is:  %s\n",p);

         else printf("\nBad filename!\n");
   }

 }


 VOLNAME.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\VOLNAME.ASM

         title   VOLNAME - displays volume label
         page    55,132
         .286
 ;
 ; VOLNAME.ASM --- displays volume label for current drive,
 ;                 demonstrates use of DosQFSInfo.
 ; Copyright (C) October 1986 Ray Duncan
 ;
 ; Build:        MASM VOLNAME;
 ;               LINK VOLNAME,,,OS2,VOLNAME;
 ;
 ; Usage:        VOLNAME

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf      equ     0ah                     ; ASCII line feed

         extrn   DosExit:far             ; references to OS/2 API
         extrn   DosQCurDisk:far
         extrn   DosQFSInfo:far
         extrn   DosWrite:far

 _vlinfo struc                           ; volume label structure
 date    dw      ?                       ; date of creation
 time    dw      ?                       ; time of creation
 count   db      ?                       ; length of volume label
 vname   db      13 dup (?)              ; volume label text
 _vlinfo ends

 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 wlen    dw      ?                       ; receives bytes written

 vlinf   _vlinfo <>                      ; volume label info
 vlinf_len equ $-vlinf

 drive   dw      0                       ; receives drive ID
 drvmap  dd      0                       ; receives drive bitmap

 msg1    db      cr,lf,'Volume in drive '
 msg1a   db      'X has no label.',cr,lf
 msg1_length equ $-msg1

 msg2    db      cr,lf
 msg2_length equ $-msg2

 msg3    db      cr,lf,'Volume in drive '
 msg3a   db      'X is '
 msg3_length equ $-msg3

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

 volname proc    far                     ; entry point from OS/2

                                         ; get current drive...
         push    ds                      ; receives drive code
         push    offset DGROUP:drive
         push    ds                      ; receives bitmap for
         push    offset DGROUP:drvmap    ; available drives
         call    DosQCurDisk             ; transfer to OS/2
         or      ax,ax                   ; did function succeed?
         jz      vol1                    ; yes, proceed
         jmp     vol5                    ; exit if function failed

 vol1:   mov     al,'A'-1                ; convert drive code to
         add     ax,drive                ; ASCII letter and store
         mov     msg3a,al                ; for output
         mov     msg1a,al

                                         ; get volume label...
         push    0                       ; drive = default
         push    2                       ; file info level 2
         push    ds                      ; buffer receives volume
         push    offset DGROUP:vlinf     ; date, time, and name
         push    vlinf_len               ; size of buffer
         call    DosQFSInfo              ; transfer to OS/2
         or      ax,ax                   ; call successful?
         jnz     vol2                    ; jump, got volume label

         cmp     vlinf.count,0           ; null string?
         jne     vol3                    ; no, go display label

 vol2:                                   ; volume has no label...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_length             ; message length
         push    ds                      ; receives actual bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2
         jmp     vol4                    ; now eixt

 vol3:                                   ; display initial message...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg3
         push    msg3_length             ; message length
         push    ds                      ; receives actual bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; display actual volume label...
         push    stdout                  ; standard output handle
         push    ds                      ; label address
         push    offset DGROUP:vlinf.vname
         mov     al,vlinf.count          ; label length
         xor     ah,ah
         push    ax
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; output CR-LF sequence...
         push    stdout                  ; standard output handle
         push    ds                      ; address of CR-LF
         push    offset DGROUP:msg2
         push    msg2_length             ; length of CR-LF
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

 vol4:                                   ; success exit point...
         push    1                       ; terminate all threads
         push    0                       ; return code=0 (success)
         call    DosExit                 ; transfer to OS/2

 vol5:                                   ; error exit point...
         push    1                       ; terminate all threads
         push    1                       ; return code=1 (error)
         call    DosExit                 ; transfer to OS/2

 volname endp

 _TEXT   ends

         end     volname



 WHEREIS.ASM
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\WHEREIS.ASM

         title        WHEREIS -- File Finder Utility
         page    55,132
         .286

 ;
 ; WHEREIS.ASM
 ;
 ; A file finder utility that searches the current drive, starting
 ; with the root directory (\), for the specified pathname.
 ; Wildcard characters can be included.
 ;
 ; This program requires the modules ARGV.ASM and ARGC.ASM.
 ;
 ; Assemble with:  C> masm whereis.asm;
 ; Link with:  C> link whereis+argv+argc,,,os2,whereis
 ;
 ; Usage is:  C> whereis pathname
 ;
 ; Copyright (C) 1988 Ray Duncan
 ;

 stdin   equ     0                       ; standard input handle
 stdout  equ     1                       ; standard output handle
 stderr  equ     2                       ; standard error handle

 cr      equ     0dh                     ; ASCII carriage return
 lf        equ        0ah                        ; ASCII linefeed

         extrn   DosChDir:far
         extrn   DosExit:far
         extrn   DosFindClose:far
         extrn   DosFindFirst:far
         extrn   DosFindNext:far
         extrn   DosQCurDir:far
         extrn   DosQCurDisk:far
         extrn   DosSelectDisk:far
         extrn   DosWrite:far

 _srec        struc                                ; search result structure..
 cdate   dw      ?                       ; date of creation
 ctime   dw      ?                       ; time of creation
 adate   dw      ?                       ; date of last access
 atime   dw      ?                       ; time of last access
 wdate   dw      ?                       ; date of last write
 wtime   dw      ?                       ; time of last write
 fsize   dd      ?                       ; file size
 falloc  dd      ?                       ; file allocation
 fattr   dw      ?                       ; file attribute
 fcount  db      ?                       ; filename count byte
 fname   db      13 dup (?)              ; ASCIIZ filename
 _srec   ends


 DGROUP  group   _DATA

 _DATA   segment word public 'DATA'

 root    db      '\',0                   ; name of root directory

 parent  db      '..',0                  ; alias for parent directory

 wild    db      '*.*',0                 ; matches all files

 sname   db      64 dup (0)              ; filename for search

 drvno   dw      0                       ; current drive
 drvmap  dd      0                       ; logical drive bitmap

 dname   db      'X:\'                   ; current drive ID
 dbuf    db      80 dup (?)              ; current directory
 dbuf_len dw     ?                       ; length of buffer

 sbuf    _srec   <>                      ; receives search results
 sbuf_len equ $-sbuf

 count   dw      0                       ; total files matched
 wlen    dw      ?                       ; receives bytes written
 shandle dw      -1                      ; directory search handle
 scount        dw        1                        ; number of files to return

 msg1    db      cr,lf
         db      'whereis: no files found'
         db      cr,lf
 msg1_len equ $-msg1

 msg2    db      cr,lf
 msg2_len equ $-msg2

 msg3    db      cr,lf
         db        'whereis: missing filename'
         db      cr,lf
 msg3_len equ $-msg3

 msg4    db      cr,lf
         db      'whereis: bad drive'
         db      cr,lf
 msg4_len equ $-msg4

 _DATA   ends


 _TEXT   segment word public 'CODE'

         assume  cs:_TEXT,ds:DGROUP

         extrn   argv:near
         extrn   argc:near

 whereis proc    far

         call    argc                    ; filename present in
         cmp     ax,2                    ; command tail?
         jae     where1                  ; jump, filename present

                                         ; no filename, exit...
         mov     dx,offset DGROUP:msg3   ; error message address
         mov     cx,msg3_len             ; message length
         jmp     where6                  ; go terminate

 where1:                                 ; get current drive...
         push    ds                      ; receives drive code
         push    offset DGROUP:drvno
         push    ds                      ; receives drive bitmap
         push    offset DGROUP:drvmap
         call    DosQCurDisk             ; transfer to OS/2

         mov     ax,1                    ; get address and length
         call    argv                    ; of filename parameter
                                         ; returns ES:BX = address
                                         ;         AX    = length
         mov     cx,ax                   ; save length in CX

         cmp     ax,2                    ; parameter length > 2?
         jle     where3                  ; no, jump
         cmp     byte ptr es:[bx+1],':'  ; drive delimiter present?
         jne     where3                  ; no, jump

         mov     al,es:[bx]              ; get ASCII drive code
         or        al,20h                        ; fold to lowercase
         xor     ah,ah
         sub     ax,'a'-1                ; convert drive code to
         mov     drvno,ax                ; binary and save it
         cmp        ax,1                        ; make sure drive valid
         jb      where2                  ; jump, bad drive
         cmp     ax,26
         ja      where2                  ; jump, bad drive

         add     bx,2                    ; advance command tail
         sub     cx,2                    ; pointer past drive code

                                         ; set drive for search...
         push    ax                      ; drive code
         call    DosSelectDisk           ; transfer to OS/2
         or      ax,ax                   ; drive OK?
         jz      where3                  ; jump, drive was valid

                                         ; bad drive, exit...
 where2: mov     dx,offset DGROUP:msg4   ; error message address
         mov     cx,msg4_len             ; message length
         jmp     where6

 where3: mov     di,offset DGROUP:sname  ; DS:DI = local buffer

 where4: mov     al,es:[bx]              ; copy filename to local
         mov     [di],al                 ; buffer byte by byte...
         inc     di
         inc     bx
         loop    where4

         mov     byte ptr [di],0         ; append null byte

         push    ds                      ; make DGROUP addressable
         pop     es                      ; with ES
         assume  es:DGROUP

         mov     dx,offset DGROUP:root   ; start searching with
         call    schdir                  ; the root directory

         cmp     count,0                 ; any matching files found?
         jne     where5                  ; yes, exit silently

                                         ; no, display 'no files'...
         push    stdout                  ; standard output handle
         push    ds                      ; message address
         push    offset DGROUP:msg1
         push    msg1_len                ; message length
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

 where5:                                 ; final exit to OS/2...
         push    1                       ; terminate all threads
         push        0                        ; return code = 0 (success)
         call    DosExit                 ; transfer to OS/2

 where6:                                 ; common error exit...
                                         ; DS:DX = msg, CX = length
         push    stderr                  ; standard output handle
         push    ds                      ; address of message
         push    dx
         push    cx                      ; length of message
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; final exit to OS/2...
         push    1                       ; terminate all threads
         push    1                       ; exit code = 1 (error)
         call    DosExit                 ; transfer to OS/2

 whereis endp


 ; SCHDIR:       search a directory for matching
 ;               files and any other directories
 ;
 ; Call with:    DS:DX = ASCIIZ directory name
 ; Returns:      nothing
 ; Uses:         all registers

 schdir  proc    near

         push    shandle                 ; save old search handle
         mov     shandle,-1              ; initialize search handle

                                         ; set search directory...
         push    ds                      ; directory name address
         push    dx
         push    0                       ; reserved DWORD 0
         push    0
         call    DosChDir                ; transfer to OS/2

         call    schfile                 ; search current directory
                                         ; for matching files

                                         ; search for directories
         mov     scount,1                ; max matches to return
         push    ds                      ; target name address
         push    offset DGROUP:wild
         push    ds                      ; receives search handle
         push    offset DGROUP:shandle
         push        10h                        ; normal + dir attribute
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset DGROUP:scount
         push    0                       ; reserved DWORD 0
         push    0
         call    DosFindFirst            ; transfer to OS/2

         or      ax,ax                   ; find anything?
         jnz     schdir3                 ; no, jump

 schdir1:                                ; found some match...
         test    sbuf.fattr,10h          ; is it a directory?
         jz      schdir2                 ; no, skip it

         cmp     sbuf.fname,'.'          ; is it . or .. entry?
         je      schdir2                 ; yes, skip it

                                         ; no, new directory found
         mov     dx,offset DGROUP:sbuf.fname
         call    schdir                  ; call self to search it

                                         ; restore old directory...
         push    ds                      ; address of '..' alias
         push    offset DGROUP:parent
         push    0                       ; reserved DWORD 0
         push    0
         call    DosChDir                ; transfer to OS/2

 schdir2:                                ; found at least one match,
                                         ; look for next match...
         mov     scount,1                ; max matches to return
         push    shandle                 ; handle from DosFindFirst
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset DGROUP:scount
         call    DosFindNext             ; transfer to OS/2

         or      ax,ax                   ; any matches found?
         jz      schdir1                 ; yes, go process it

 schdir3:                                ; end of search...
         push    shandle                 ; close search handle
         call    DosFindClose            ; transfer to OS/2

         pop     shandle                 ; restore previous handle

         ret                             ; back to caller

 schdir  endp


 ; SCHFILE:      search current directory for
 ;               files matching string in 'sname'
 ;
 ; Call with:    nothing
 ; Returns:      nothing
 ; Uses:         all registers

 schfile proc    near

         push    shandle                 ; save previous handle
         mov     shandle,-1              ; initialize search handle

         mov     scount,1                ; max matches to return
         push    ds                      ; name to match
         push    offset DGROUP:sname
         push    ds                      ; receives search handle
         push    offset DGROUP:shandle
         push    0h                      ; attribute=normal files
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset DGROUP:scount
         push    0                       ; reserved DWORD 0
         push    0
         call    DosFindFirst            ; transfer to OS/2

         or      ax,ax                   ; any matches found?
         jnz     schfile3                ; no, terminate search

 schfile1:                               ; found matching file...
         call    pfile                   ; display its name

                                         ; look for next match...
         push    shandle                 ; handle from DosFindFirst
         push    ds                      ; result buffer address
         push    offset DGROUP:sbuf
         push    sbuf_len                ; result buffer length
         push    ds                      ; receives match count
         push    offset DGROUP:scount
         call    DosFindNext             ; transfer to OS/2

         or      ax,ax                   ; any more matches?
         jz      schfile1                ; yes, go display filename

 schfile3:                               ; end of search...
         push    shandle                 ; close search handle
         call    DosFindClose            ; transfer to OS/2

         pop     shandle                 ; restore previous handle
         ret                             ; return to caller

 schfile endp


 ; PFILE:        display current drive and directory,
 ;               followed by filename from 'sbuf.fname'
 ;
 ; Call with:    nothing
 ; Returns:      nothing
 ; Uses:         all registers

 pfile   proc    near

         inc     count                   ; count matched files

         call    pdir                    ; display drive:path

                                         ; fold name to lower case
         mov     bx,offset DGROUP:sbuf.fname
         call    makelc

                                         ; display filename...
         push    stdout                  ; standard output handle
         push    ds                      ; filename address
         push    offset DGROUP:sbuf.fname
         mov     al,sbuf.fcount          ; filename length
         xor     ah,ah
         push    ax
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

                                         ; send newline sequence...
         push    stdout                  ; standard output handle
         push    ds                      ; address of newline
         push    offset DGROUP:msg2
         push    msg2_len                ; length of newline
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         ret                             ; return to caller

 pfile   endp


 ; PDIR:         display current drive and directory
 ;
 ; Call with:    nothing
 ; Returns:      nothing
 ; Uses:         AX, BX, CX, DI, ES

 pdir    proc    near

         mov     ax,drvno                ; convert binary drive
         add     al,'A'-1                ; code to ASCII drive
         mov     dname,al                ; and store it for output

         mov     dbuf_len,dbuf_len-dbuf  ; initialize length of
                                         ; directory buffer

                                         ; get current directory...
         push    0                       ; drive 0 = default
         push    ds                      ; receives ASCIIZ path
         push    offset DGROUP:dbuf
         push    ds                      ; contains buffer length
         push    offset DGROUP:dbuf_len
         call    DosQCurDir              ; transfer to OS/2

         mov     di,offset DGROUP:dbuf   ; address of path

         cmp     byte ptr [di],0         ; is path = root?
         je      pdir1                   ; yes, jump

         mov     cx,dbuf_len-dbuf        ; no, scan for null
         xor     al,al                   ; byte at end of path...
         repne scasb

         mov     byte ptr [di-1],'\'     ; append a backslash

 pdir1:  mov     bx,offset DGROUP:dname  ; fold everything to
         call        makelc                        ; lowercase

                                         ; now display drive:path...
         push    stdout                  ; standard output handle
         push    ds                      ; address of pathname
         push    offset DGROUP:dname
         sub        di,offset DGROUP:dname        ; length of drive and path
         push    di
         push    ds                      ; receives bytes written
         push    offset DGROUP:wlen
         call    DosWrite                ; transfer to OS/2

         ret                             ; back to caller

 pdir    endp


 ; MAKELC:        convert ASCIIZ string to lowercase
 ;
 ; Call with:    DS:BX = string address
 ; Returns:      nothing
 ; Uses:         BX

 makelc  proc    near

 make1:  cmp     byte ptr [bx],0         ; end of string?
         je      make3                   ; jump if end

         cmp     byte ptr [bx],'A'       ; check next character
         jb        make2                        ; jump, not uppercase
         cmp     byte ptr [bx],'Z'
         ja        make2                        ; jump, not uppercase

         or        byte ptr [bx],20h        ; fold to lowercase

 make2:  inc     bx                      ; advance through string
         jmp     make1

 make3:  ret                             ; back to caller

 makelc  endp

 _TEXT   ends

         end     whereis


 WHEREIS.C
 CD-ROM Disc Path:   \SAMPCODE\ADVOS2\CH09\WHEREIS.C

 /*
         WHEREIS.C

         A file finder utility that searches the current drive, starting
         with the root directory (\), for the specified pathname.
         Wildcard characters can be included.

         Compile with:  C> cl whereis.c

         Usage is:  C> whereis pathname

         Copyright (C) 1988 Ray Duncan
 */


 #include <stdio.h>
 #include <string.h>

 #define API unsigned extern far pascal

 API DosChDir(char far *, unsigned long);
 API DosFindClose(unsigned);
 API DosFindFirst(char far *, unsigned far *, unsigned, void far *,
                  int, int far *, unsigned long);
 API DosFindNext(unsigned, void far *, int, int far *);
 API DosQCurDisk(int far *, unsigned long far *);
 API DosQCurDir(int, void far *, int far *);
 API DosSelectDisk(int);
 API DosWrite(unsigned, void far *, unsigned, unsigned far *);

 NORM      0x00                  /* file attribute bits */
 #define RD_ONLY   0x01
 #define HIDDEN    0x02
 #define SYSTEM    0x04
 #define DIR       0x10
 #define ARCHIVE   0x20

 struct _srec {                          /* used by DosFindFirst */
                     unsigned cdate;     /* and DosFindNext */
                     unsigned ctime;
                     unsigned adate;
                     unsigned atime;
                     unsigned wdate;
                     unsigned wtime;
                     long fsize;
                     long falloc;
                     unsigned fattr;
                     char fcount;
                     char fname[13];
              }
                 sbuf;                   /* receives search results */

 int drvno;                              /* current drive */
 int count = 0;                          /* total files matched */
 char sname[80];                         /* target pathname */


 main(int argc, char *argv[])
 {
     unsigned long drvmap;               /* logical drive map */

     if(argc < 2)                        /* exit if no parameters */
     {
         printf("\nwhereis: missing filename\n");
         exit(1);
     }

     DosQCurDisk(&drvno, &drvmap);       /* get current drive */

                                         /* any drive specified? */
     if(((strlen(argv[1])) >= 2) && ((argv[1])[1] == ':'))
     {
                                         /* get binary drive code */
         drvno = ((argv[1]) [0] | 0x20) - ('a'-1);

         if(DosSelectDisk(drvno))        /* select drive or exit */
         {
             printf("\nwhereis: bad drive\n");
             exit(1);
         }

         argv[1] += 2;                   /* advance past drive */
     }

     strcpy(sname,argv[1]);              /* save search target */

     schdir("\\");                       /* start search with root */

                                         /* advise if no matches */
     if(count == 0) printf("\nwhereis: no files\n");
 }


 /*
     SCHDIR: search directory for matching files and
             any other directories
 */

 schdir(char *dirname)
 {
     unsigned shan = -1;                 /* search handle */
     int scnt = 1;                       /* max search matches */

     DosChDir(dirname, 0L);              /* select new directory */

     schfile();                          /* find and list files */

                                         /* search for directories */
     if(!DosFindFirst("*.*", &shan, NORM|DIR, &sbuf, sizeof(sbuf), &scnt, 0L))
     {
         do                              /* if found directory other */
         {                               /* than . and .. aliases */
             if((sbuf.fattr & DIR) && (sbuf.fname[0] != '.'))
             {
                 schdir(sbuf.fname);     /* search the directory */
                 DosChDir("..", 0L);     /* restore old directory */
             }
                                         /* look for more directories */
         } while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0);
     }

     DosFindClose(shan);                 /* close search handle */
 }


 /*
     SCHFILE: search current directory for files
              matching string in 'sname'
 */

 schfile()
 {
     unsigned shan = -1;                 /* search handle */
     int scnt = 1;                       /* max search matches */

     if(!DosFindFirst(sname, &shan, NORM, &sbuf, sizeof(sbuf), &scnt, 0L))
     {
         do pfile();                     /* list matching files */
         while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0);
     }

     DosFindClose(shan);                 /* close search handle */
 }


 /*
     PFILE: display current drive and directory,
            followed by filename from 'sbuf.fname'
 */

 pfile()
 {
     count++;                            /* count matched files */
     pdir();                                /* list drive and path */
     printf("%s\n", strlwr(sbuf.fname)); /* list filename */
 }


 /*
     PDIR: display current drive and directory
 */

 pdir()
 {
     char dbuf[80];                        /* receives current dir */
     int dlen = sizeof(dbuf);            /* length of buffer */

     DosQCurDir(0, dbuf, &dlen);         /* get current directory */

     if(strlen(dbuf) != 0)               /* add backslash to */
         strcat(dbuf,"\\");              /* directory if not root */

                                         /* display drive and path */
     printf("%c:\\%s", drvno+'a'-1, strlwr(dbuf));
 }

 Programming the Presentation Manager - Sample Code


 ABOUTBOX.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\ABOUTBOX.C

 /*-----------------------------------------------------------
    ABOUTBOX.C -- Demonstration of About Box Dialog Procedure
   -----------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "aboutbox.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY AboutDlgProc  (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "AboutBox" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ICON ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_NEW:
                     case IDM_OPEN:
                     case IDM_SAVE:
                     case IDM_SAVEAS:
                          WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                          return 0 ;

                     case IDM_ABOUT:
                          WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc,
                                     NULL, IDD_ABOUT, NULL) ;
                          return 0 ;
                     }
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                     case DID_CANCEL:
                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }


 ALTWIND.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\ALTWIND.C

 /*------------------------------------------
    ALTWIND.C -- Alternate and Winding Modes
  -------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "AltWind" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static POINTL aptlFigure[10] = { 10,30, 50,30, 50,90, 90,90, 90,50,
                                       30,50, 30,10, 70,10, 70,70, 10,70 } ;
      static SHORT  cxClient, cyClient ;
      HPS           hps ;
      POINTL        aptl[10] ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;
                GpiSetPattern (hps, PATSYM_HALFTONE) ;

                          /*---------------------
                             Alternate Fill Mode
                            ---------------------*/

                for (sIndex = 0 ; sIndex < 10 ; sIndex++)
                     {
                     aptl[sIndex].x = cxClient * aptlFigure[sIndex].x / 200 ;
                     aptl[sIndex].y = cyClient * aptlFigure[sIndex].y / 100 ;
                     }

                GpiBeginArea (hps, BA_BOUNDARY | BA_ALTERNATE) ;
                GpiMove (hps, aptl) ;
                GpiPolyLine (hps, 9L, aptl + 1) ;
                GpiEndArea (hps) ;

                          /*-------------------
                             Winding Fill Mode
                            -------------------*/

                for (sIndex = 0 ; sIndex < 10 ; sIndex++)
                     aptl[sIndex].x += cxClient / 2 ;

                GpiBeginArea (hps, BA_BOUNDARY | BA_WINDING) ;
                GpiMove (hps, aptl) ;
                GpiPolyLine (hps, 9L, aptl + 1) ;
                GpiEndArea (hps) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 AVIO1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP07\AVIO1.C

 /*-----------------------------------------
    AVIO1.C -- Advanced VIO Display of Text
   -----------------------------------------*/

 #define INCL_WIN
 #define INCL_VIO
 #define INCL_AVIO
 #include <os2.h>
 #include <string.h>

 #define VIDEOWIDTH 40

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Avio1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  *aszAlice [] = {
                                  "Alice was beginning to get very tired of",
                                  "sitting by her sister on the bank and of",
                                  "having nothing to do: once or twice she",
                                  "had peeped into the book her sister was",
                                  "reading, but it had no pictures or ",
                                  "conversations in it, \"and what is the",
                                  "use of a book,\" thought Alice, \"without",
                                  "pictures or conversations?\""
                                  } ;
      static HPS   hps ;
      static HVPS  hvps ;
      static SHORT sNumLines = sizeof aszAlice / sizeof aszAlice[0] ;
      HDC          hdc ;
      SHORT        sRow ;
      SIZEL        sizl ;

      switch (msg)
           {
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                sizl.cx = sizl.cy = 0 ;
                hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAULT
                                                    GPIT_MICRO | GPIA_ASSOC) ;

                VioCreatePS (&hvps, sNumLines, VIDEOWIDTH, 0, 1, NULL) ;
                VioAssociate (hdc, hvps) ;

                for (sRow = 0 ; sRow < sNumLines ; sRow++)
                     VioWrtCharStr (aszAlice[sRow],
                                    strlen (aszAlice[sRow]),
                                    sRow, 0, hvps) ;
                return 0 ;

           case WM_SIZE:
                WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
                return 0 ;

           case WM_PAINT:
                WinBeginPaint (hwnd, hps, NULL) ;
                GpiErase (hps) ;

                VioShowBuf (0, 2 * sNumLines * VIDEOWIDTH, hvps) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                VioAssociate (NULL, hvps) ;
                VioDestroyPS (hvps) ;
                GpiDestroyPS (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 AVIO2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP07\AVIO2.C

 /*-----------------------------------------
    AVIO2.C -- Advanced VIO Display of Text
   -----------------------------------------*/

 #define INCL_WIN
 #define INCL_VIO
 #define INCL_AVIO
 #include <os2.h>

 #define VIDEOWIDTH 40

 typedef struct
      {
      CHAR ch ;
      CHAR attr ;
      }
      VIDEO [][VIDEOWIDTH] ;

 typedef VIDEO FAR *PVIDEO ;

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR    szClientClass [] = "Avio2" ;
      static ULONG   flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                    FCF_SIZEBORDER    | FCF_MINMAX  |
                                    FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ            hmq ;
      HWND           hwndFrame, hwndClient ;
      QMSG           qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  *aszAlice [] = {
                                  "Alice was beginning to get very tired of",
                                  "sitting by her sister on the bank and of",
                                  "having nothing to do: once or twice she",
                                  "had peeped into the book her sister was",
                                  "reading, but it had no pictures or",
                                  "conversations in it, \"and what is the",
                                  "use of a book,\" thought Alice, \"without",
                                  "pictures or conversations?\""
                                  } ;
      static HPS    hps ;
      static HVPS   hvps ;
      static PVIDEO pvideo ;
      static SHORT  sNumLines = sizeof aszAlice / sizeof aszAlice[0] ;
      static USHORT usVideoLength ;
      HDC           hdc ;
      RECTL         rcl ;
      SHORT         sRow, sCol ;
      SIZEL         sizl ;
      ULONG         ulVideoBuffer ;

      switch (msg)
           {
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                sizl.cx = sizl.cy = 0 ;
                hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAULT
                                                    GPIT_MICRO | GPIA_ASSOC) ;

                VioCreatePS (&hvps, sNumLines, VIDEOWIDTH, 0, 1, NULL) ;
                VioAssociate (hdc, hvps) ;

                VioGetBuf (&ulVideoBuffer, &usVideoLength, hvps) ;
                pvideo = (PVIDEO) ulVideoBuffer ;

                for (sRow = 0 ; sRow < sNumLines ; sRow++)
                     for (sCol = 0 ; sCol < VIDEOWIDTH ; sCol++)
                          (*pvideo) [sRow][sCol].attr = '\x1E' ;

                for (sRow = 0 ; sRow < sNumLines ; sRow++)
                     for (sCol = 0 ; aszAlice [sRow][sCol] ; sCol++)
                          (*pvideo) [sRow][sCol].ch = aszAlice [sRow][sCol] ;

                return 0 ;

           case WM_SIZE:
                WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
                return 0 ;

           case WM_PAINT:
                WinBeginPaint (hwnd, hps, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;
                WinFillRect (hps, &rcl, CLR_DARKBLUE) ;

                VioShowBuf (0, usVideoLength, hvps) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                VioAssociate (NULL, hvps) ;
                VioDestroyPS (hvps) ;
                GpiDestroyPS (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BEEPER1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP10\BEEPER1.C

 /*---------------------------------------
    BEEPER1.C -- Timer Demo Program No. 1
   ---------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 #define ID_TIMER 1

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static char  szClientClass [] = "Beeper1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinStartTimer (hab, hwndClient, ID_TIMER, 1000) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinStopTimer (hab, hwndClient, ID_TIMER) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL fFlipFlop ;
      HPS         hps ;
      RECTL       rcl ;

      switch (msg)
           {
           case WM_TIMER:
                WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                fFlipFlop = !fFlipFlop ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;
                WinFillRect (hps, &rcl, fFlipFlop ? CLR_BLUE : CLR_RED) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BEEPER2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP10\BEEPER2.C

 /*---------------------------------------
    BEEPER2.C -- Timer Demo Program No. 2
   ---------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 BOOL fFlipFlop ;

 int main (void)
      {
      static char  szClientClass [] = "Beeper2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;
      USHORT       idTimer ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      idTimer = WinStartTimer (hab, NULL, 0, 1000) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           {
           if (qmsg.msg == WM_TIMER && SHORT1FROMMP (qmsg.mp1) == idTimer)
                {
                WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                fFlipFlop = !fFlipFlop ;
                WinInvalidateRect (hwndClient, NULL, FALSE) ;
                }
           else
                WinDispatchMsg (hab, &qmsg) ;
           }

      WinStopTimer (hab, NULL, idTimer) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      HPS   hps ;
      RECTL rcl ;

      switch (msg)
           {
           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;
                WinFillRect (hps, &rcl, fFlipFlop ? CLR_BLUE : CLR_RED) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BIGJOB.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB.C

 /*--------------------------------------------------------------------
    BIGJOB.C -- Common functions used in BIGJOB1, BIGJOB2, and BIGJOB3
   --------------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include <math.h>
 #include <stdio.h>

 double Savage (double A)
      {
      return tan (atan (exp (log (sqrt (A * A))))) + 1.0 ;
      }

 VOID CheckMenuItem (HWND hwnd, SHORT sMenuItem, BOOL fCheck)
      {
      HWND  hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
      HWND  hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;

      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (sMenuItem, TRUE),
                  MPFROM2SHORT (MIA_CHECKED, fCheck ? MIA_CHECKED : 0)) ;
      }

 VOID EnableMenuItem (HWND hwnd, SHORT sMenuItem, BOOL fEnable)
      {
      HWND  hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
      HWND  hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;

      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (sMenuItem, TRUE),
                  MPFROM2SHORT (MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)) ;
      }

 VOID PaintWindow (HWND hwnd, SHORT sStatus, LONG lCalcRep, ULONG ulTime)
      {
      static CHAR *szMessage [3] = { "Ready", "Working...",
                                     "%ld repetitions in %lu msec." } ;
      CHAR        szBuffer [60] ;
      HPS         hps ;
      RECTL       rcl ;

      hps = WinBeginPaint (hwnd, NULL, NULL) ;
      WinQueryWindowRect (hwnd, &rcl) ;

      sprintf (szBuffer, szMessage [sStatus], lCalcRep, ulTime) ;
      WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
                   DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

      WinEndPaint (hps) ;
      }


 BIGJOB1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB1.C

 /*-------------------------------------------------------
    BIGJOB1.C -- Naive approach to lengthy processing job
  --------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "bigjob.h"

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BigJob1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ            hmq ;
      HWND           hwndFrame, hwndClient ;
      QMSG           qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc,
                        CS_SYNCPAINT | CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - The Bad Program",
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static LONG  lCalcRep, lRepAmts [] = { 10, 100, 1000, 10000, 100000 } ;
      static SHORT sCurrentRep = IDM_10 ;
      static SHORT sStatus = STATUS_READY ;
      static ULONG ulElapsedTime ;
      double       A ;
      LONG         lRep ;

      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_10:
                     case IDM_100:
                     case IDM_1000:
                     case IDM_10000:
                     case IDM_100000:
                          CheckMenuItem (hwnd, sCurrentRep, FALSE) ;
                          sCurrentRep = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwnd, sCurrentRep, TRUE) ;
                          return 0 ;

                     case IDM_START:
                          EnableMenuItem (hwnd, IDM_START, FALSE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;

                          sStatus = STATUS_WORKING ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_WAIT, FALSE)) ;

                          if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT)
                                    == 0)
                               WinShowPointer (HWND_DESKTOP, TRUE) ;

                          lCalcRep = lRepAmts [sCurrentRep - IDM_10] ;
                          ulElapsedTime = WinGetCurrentTime (hab) ;

                          for (A = 1.0, lRep = 0 ; lRep < lCalcRep ; lRep++)
                               A = Savage (A) ;

                          ulElapsedTime = WinGetCurrentTime (hab) -
                                         ulElapsedTime ;

                          if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT)
                                    == 0)
                               WinShowPointer (HWND_DESKTOP, FALSE) ;

                          WinSetPointer (HWND_DESKTOP,
                                    WinQuerySysPointer (HWND_DESKTOP,
                                                        SPTR_ARROW, FALSE)) ;
                          sStatus = STATUS_DONE ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          EnableMenuItem (hwnd, IDM_START, TRUE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                          return 0 ;

                     case IDM_ABORT:     // Not much we can do here
                          return 0 ;
                     }
                break ;

           case WM_PAINT:
                PaintWindow (hwnd, sStatus, lCalcRep, ulElapsedTime) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BIGJOB2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB2.C

 /*-------------------------------------------------------
    BIGJOB2.C -- Timer approach to lengthy processing job
  --------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "bigjob.h"

 #define ID_TIMER 1

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BigJob2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc,
                        CS_SYNCPAINT | CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - The Timer",
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static double A ;
      static LONG   lRep, lCalcRep,
                    lRepAmts [] = { 10, 100, 1000, 10000, 100000 } ;
      static SHORT  sCurrentRep = IDM_10 ;
      static SHORT  sStatus = STATUS_READY ;
      static ULONG  ulElapsedTime ;

      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_10:
                     case IDM_100:
                     case IDM_1000:
                     case IDM_10000:
                     case IDM_100000:
                          CheckMenuItem (hwnd, sCurrentRep, FALSE) ;
                          sCurrentRep = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwnd, sCurrentRep, TRUE) ;
                          return 0 ;

                     case IDM_START:
                          if (!WinStartTimer (hab, hwnd, ID_TIMER, 0))
                               {
                               WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                               return 0 ;
                               }
                          EnableMenuItem (hwnd, IDM_START, FALSE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;

                          sStatus = STATUS_WORKING ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          lCalcRep = lRepAmts [sCurrentRep - IDM_10] ;
                          ulElapsedTime = WinGetCurrentTime (hab) ;
                          A = 1.0 ;
                          lRep = 0 ;

                          return 0 ;

                     case IDM_ABORT:
                          WinStopTimer (hab, hwnd, ID_TIMER) ;

                          sStatus = STATUS_READY ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          EnableMenuItem (hwnd, IDM_START, TRUE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                          return 0 ;
                     }
                break ;

           case WM_TIMER:
                A = Savage (A) ;

                if (++lRep == lCalcRep)
                     {
                     ulElapsedTime = WinGetCurrentTime (hab) -
                                         ulElapsedTime ;

                     WinStopTimer (hab, hwnd, ID_TIMER) ;

                     sStatus = STATUS_DONE ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;

                     EnableMenuItem (hwnd, IDM_START, TRUE) ;
                     EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                     }
                return 0 ;

           case WM_PAINT:
                PaintWindow (hwnd, sStatus, lCalcRep, ulElapsedTime) ;
                return 0 ;

           case WM_DESTROY:
                if (sStatus == STATUS_WORKING)
                     WinStopTimer (hab, hwnd, ID_TIMER) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BIGJOB3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB3.C

 /*--------------------------------------------------------------
    BIGJOB3.C -- Peek Message approach to lengthy processing job
  ---------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "bigjob.h"

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BigJob3" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - Message Peeking",
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL   fContinueCalc = FALSE ;
      static LONG   lCalcRep, lRepAmts [] = { 10, 100, 1000, 10000, 100000 } ;
      static SHORT  sStatus = STATUS_READY ;
      static SHORT  sCurrentRep = IDM_10 ;
      static ULONG  ulElapsedTime ;
      double        A ;
      LONG          lRep ;
      QMSG          qmsg ;

      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_10:
                     case IDM_100:
                     case IDM_1000:
                     case IDM_10000:
                     case IDM_100000:
                          CheckMenuItem (hwnd, sCurrentRep, FALSE) ;
                          sCurrentRep = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwnd, sCurrentRep, TRUE) ;
                          return 0 ;

                     case IDM_START:
                          EnableMenuItem (hwnd, IDM_START, FALSE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;

                          sStatus = STATUS_WORKING ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          lCalcRep = lRepAmts [sCurrentRep - IDM_10] ;
                          fContinueCalc = TRUE ;
                          ulElapsedTime = WinGetCurrentTime (hab) ;

                          qmsg.msg = WM_NULL ;

                          for (A = 1.0, lRep = 0 ; lRep < lCalcRep ; lRep++)
                               {
                               A = Savage (A) ;

                               while (WinPeekMsg (hab, &qmsg, NULL, 0, 0,
                                                  PM_NOREMOVE))
                                    {
                                    if (qmsg.msg == WM_QUIT)
                                         break ;

                                    WinGetMsg (hab, &qmsg, NULL, 0, 0) ;
                                    WinDispatchMsg (hab, &qmsg) ;

                                    if (!fContinueCalc)
                                         break ;
                                    }
                               if (!fContinueCalc || qmsg.msg == WM_QUIT)
                                    break ;
                               }
                          ulElapsedTime = WinGetCurrentTime (hab) -
                                                   ulElapsedTime ;

                          if (!fContinueCalc || qmsg.msg == WM_QUIT)
                               sStatus = STATUS_READY ;
                          else
                               sStatus = STATUS_DONE ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;

                          EnableMenuItem (hwnd, IDM_START, TRUE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                          return 0 ;

                     case IDM_ABORT:
                          fContinueCalc = FALSE ;
                          return 0 ;
                     }
                break ;

           case WM_PAINT:
                PaintWindow (hwnd, sStatus, lCalcRep, ulElapsedTime) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BIGJOB4.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB4.C

 /*---------------------------------------------------------------
    BIGJOB4.C -- Second thread approach to lengthy processing job
  ----------------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_DOS
 #include <os2.h>
 #include <mt\process.h>
 #include <mt\stdlib.h>
 #include "bigjob.h"

 VOID _CDECL FAR CalcThread (PCALCPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BigJob4" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - A Second Thread",
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CALCPARAM cp ;
      static LONG      lRepAmts [] = { 10, 100, 1000, 10000, 100000 } ;
      static SHORT     sCurrentRep = IDM_10 ;
      static SHORT     sStatus = STATUS_READY ;
      static TID       tidCalc ;
      static ULONG     ulElapsedTime ;
      static VOID      *pThreadStack ;

      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_10:
                     case IDM_100:
                     case IDM_1000:
                     case IDM_10000:
                     case IDM_100000:
                          CheckMenuItem (hwnd, sCurrentRep, FALSE) ;
                          sCurrentRep = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwnd, sCurrentRep, TRUE) ;
                          return 0 ;

                     case IDM_START:
                          if (NULL == (pThreadStack = malloc (STACKSIZE)))
                               {
                               WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                               return 0 ;
                               }

                          cp.hwnd = hwnd ;
                          cp.lCalcRep = lRepAmts [sCurrentRep - IDM_10] ;
                          cp.fContinueCalc = TRUE ;

                          if (-1 == (tidCalc = _beginthread (CalcThread,
                                         pThreadStack, STACKSIZE, &cp)))
                               {
                               free (pThreadStack) ;
                               WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                               return 0 ;
                               }

                          sStatus = STATUS_WORKING ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          EnableMenuItem (hwnd, IDM_START, FALSE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
                          return 0 ;

                     case IDM_ABORT:
                          cp.fContinueCalc = FALSE ;
                          EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                          return 0 ;
                     }
                break ;

           case WM_CALC_DONE:
                sStatus = STATUS_DONE ;
                ulElapsedTime = LONGFROMMP (mp1) ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                EnableMenuItem (hwnd, IDM_START, TRUE) ;
                EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                free (pThreadStack) ;
                return 0 ;

           case WM_CALC_ABORTED:
                sStatus = STATUS_READY ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                EnableMenuItem (hwnd, IDM_START, TRUE) ;
                free (pThreadStack) ;
                return 0 ;

           case WM_PAINT:
                PaintWindow (hwnd, sStatus, cp.lCalcRep, ulElapsedTime) ;
                return 0 ;

           case WM_DESTROY:
                if (sStatus = STATUS_WORKING)
                     DosSuspendThread (tidCalc) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 VOID _CDECL FAR CalcThread (PCALCPARAM pcp)
      {
      double A ;
      LONG   lRep, lTime ;

      lTime = WinGetCurrentTime (hab) ;

      for (A = 1.0, lRep = 0 ; lRep < pcp->lCalcRep &&
                               pcp->fContinueCalc ; lRep++)
           A = Savage (A) ;

      DosEnterCritSec () ;     // So thread is dead when message retrieved

      if (pcp->fContinueCalc)
           {
           lTime = WinGetCurrentTime (hab) - lTime ;
           WinPostMsg (pcp->hwnd, WM_CALC_DONE, MPFROMLONG (lTime), NULL) ;
           }
      else
           WinPostMsg (pcp->hwnd, WM_CALC_ABORTED, NULL, NULL) ;

      _endthread () ;
      }


 BIGJOB5.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOB5.C

 /*--------------------------------------------------
    BIGJOB5.C -- Second thread and semaphore trigger
  ---------------------------------------------------*/

 #define INCL_WIN
 #define INCL_DOS
 #include <os2.h>
 #include <mt\process.h>
 #include <mt\stdlib.h>
 #include "bigjob.h"

 VOID _CDECL FAR CalcThread (PCALCPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BigJob5" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - Second Thread with Semaphore",
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CALCPARAM cp ;
      static INT       iThreadStack [STACKSIZE / 2] ;
      static LONG      lRepAmts [] = { 10, 100, 1000, 10000, 100000 } ;
      static SHORT     sCurrentRep = IDM_10 ;
      static SHORT     sStatus = STATUS_READY ;
      static TID       tidCalc ;
      static ULONG     ulElapsedTime ;

      switch (msg)
           {
           case WM_CREATE:
                cp.hwnd = hwnd ;
                DosSemSet (&cp.ulSemTrigger) ;

                tidCalc = _beginthread (CalcThread, iThreadStack,
                                        STACKSIZE, &cp) ;
                return 0 ;

           case WM_INITMENU:
                if (tidCalc == -1 && SHORT1FROMMP (mp1) == IDM_ACTION)
                     EnableMenuItem (hwnd, IDM_START, FALSE) ;
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_10:
                     case IDM_100:
                     case IDM_1000:
                     case IDM_10000:
                     case IDM_100000:
                          CheckMenuItem (hwnd, sCurrentRep, FALSE) ;
                          sCurrentRep = COMMANDMSG(&msg)->cmd ;
                          CheckMenuItem (hwnd, sCurrentRep, TRUE) ;
                          return 0 ;

                     case IDM_START:
                          cp.lCalcRep = lRepAmts [sCurrentRep - IDM_10] ;
                          cp.fContinueCalc = TRUE ;
                          DosSemClear (&cp.ulSemTrigger) ;

                          sStatus = STATUS_WORKING ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          EnableMenuItem (hwnd, IDM_START, FALSE) ;
                          EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
                          return 0 ;

                     case IDM_ABORT:
                          cp.fContinueCalc = FALSE ;
                          EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                          return 0 ;
                     }
                break ;

           case WM_CALC_DONE:
                sStatus = STATUS_DONE ;
                ulElapsedTime = LONGFROMMP (mp1) ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                EnableMenuItem (hwnd, IDM_START, TRUE) ;
                EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
                return 0 ;

           case WM_CALC_ABORTED:
                sStatus = STATUS_READY ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                EnableMenuItem (hwnd, IDM_START, TRUE) ;
                return 0 ;

           case WM_PAINT:
                PaintWindow (hwnd, sStatus, cp.lCalcRep, ulElapsedTime) ;
                return 0 ;

           case WM_DESTROY:
                if (sStatus = STATUS_WORKING)
                     DosSuspendThread (tidCalc) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 VOID _CDECL FAR CalcThread (PCALCPARAM pcp)
      {
      double A ;
      LONG   lRep, lTime ;

      while (TRUE)
           {
           DosSemWait (&pcp->ulSemTrigger, SEM_INDEFINITE_WAIT) ;

           lTime = WinGetCurrentTime (hab) ;

           for (A = 1.0, lRep = 0 ; lRep < pcp->lCalcRep &&
                                    pcp->fContinueCalc ; lRep++)
                A = Savage (A) ;

           DosSemSet (&pcp->ulSemTrigger) ;

           if (pcp->fContinueCalc)
                {
                lTime = WinGetCurrentTime (hab) - lTime ;
                WinPostMsg (pcp->hwnd, WM_CALC_DONE, MPFROMLONG (lTime), NULL)
                }
           else
                WinPostMsg (pcp->hwnd, WM_CALC_ABORTED, NULL, NULL) ;
           }
      }


 BIGJOBMT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP17\BIGJOBMT.C

 /*------------------------------------------------------------
    BIGJOBMT.C -- Common functions used in BIGJOB4 and BIGJOB5
   ------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include <mt\math.h>
 #include <mt\stdio.h>

 double Savage (double A)
      {
      return tan (atan (exp (log (sqrt (A * A))))) + 1.0 ;
      }

 VOID CheckMenuItem (HWND hwnd, SHORT sMenuItem, BOOL fCheck)
      {
      HWND  hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
      HWND  hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;

      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (sMenuItem, TRUE),
                  MPFROM2SHORT (MIA_CHECKED, fCheck ? MIA_CHECKED : 0)) ;
      }

 VOID EnableMenuItem (HWND hwnd, SHORT sMenuItem, BOOL fEnable)
      {
      HWND  hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
      HWND  hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;

      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (sMenuItem, TRUE),
                  MPFROM2SHORT (MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)) ;
      }

 VOID PaintWindow (HWND hwnd, SHORT sStatus, LONG lCalcRep, ULONG ulTime)
      {
      static CHAR *szMessage [3] = { "Ready", "Working...",
                                     "%ld repetitions in %lu msec." } ;
      CHAR        szBuffer [60] ;
      HPS         hps ;
      RECTL       rcl ;

      hps = WinBeginPaint (hwnd, NULL, NULL) ;
      WinQueryWindowRect (hwnd, &rcl) ;

      sprintf (szBuffer, szMessage [sStatus], lCalcRep, ulTime) ;
      WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
                   DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

      WinEndPaint (hps) ;
      }


 BITCAT1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\BITCAT1.C

 /*------------------------------------------
    BITCAT1.C -- Bitmap Creation and Display
   ------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include "bitcat.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BitCat1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HBITMAP   hbm ;
      BITMAPINFO       *pbmi ;
      BITMAPINFOHEADER bmp ;
      HPS              hps ;
      RECTL            rcl ;

      switch (msg)
           {
           case WM_CREATE:

                          /*-----------------------------------
                             Create 32-by-32 monochrome bitmap
                            -----------------------------------*/

                bmp.cbFix     = sizeof bmp ;
                bmp.cx        = 32 ;
                bmp.cy        = 32 ;
                bmp.cPlanes   = 1 ;
                bmp.cBitCount = 1 ;

                pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;

                pbmi->cbFix     = sizeof bmp ;
                pbmi->cx        = 32 ;
                pbmi->cy        = 32 ;
                pbmi->cPlanes   = 1 ;
                pbmi->cBitCount = 1;

                pbmi->argbColor[0].bBlue  = 0 ;
                pbmi->argbColor[0].bGreen = 0 ;
                pbmi->argbColor[0].bRed   = 0 ;
                pbmi->argbColor[1].bBlue  = 0xFF ;
                pbmi->argbColor[1].bGreen = 0xFF ;
                pbmi->argbColor[1].bRed   = 0xFF ;

                hps = WinGetPS (hwnd) ;
                hbm = GpiCreateBitmap (hps, &bmp, CBM_INIT, abBitCat, pbmi) ;

                WinReleasePS (hps) ;
                free (pbmi) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
                               CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;

                WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
                               CLR_NEUTRAL, CLR_BACKGROUND, DBM_NORMAL) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BITCAT2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\BITCAT2.C

 /*------------------------------------------
    BITCAT2.C -- Bitmap Creation and Display
   ------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include "bitcat.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BitCat2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HBITMAP   hbm ;
      static HDC       hdcMemory ;
      static HPS       hpsMemory ;
      static SHORT     cxClient, cyClient ;
      BITMAPINFO       *pbmi ;
      BITMAPINFOHEADER bmp ;
      HPS              hps ;
      POINTL           aptl [4] ;
      SIZEL            sizl ;

      switch (msg)
           {
           case WM_CREATE:

                          /*-------------------------------------------------
                             Open memory DC and create PS associated with it
                            -------------------------------------------------*

                hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

                sizl.cx = 0 ;
                sizl.cy = 0 ;

                hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                                         PU_PELS    | GPIF_DEFAULT |
                                         GPIT_MICRO | GPIA_ASSOC) ;

                          /*------------------------
                             Create 32 by 32 bitmap
                            ------------------------*/

                bmp.cbFix     = sizeof bmp ;
                bmp.cx        = 32 ;
                bmp.cy        = 32 ;
                bmp.cPlanes   = 1 ;
                bmp.cBitCount = 1 ;

                hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, NULL, NULL) ;

                          /*------------------------------
                             Select bitmap into memory PS
                            ------------------------------*/

                GpiSetBitmap (hpsMemory, hbm) ;

                          /*-------------------------------------
                             Set bitmap bits from abBitCat array
                            -------------------------------------*/

                pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;

                pbmi->cbFix     = sizeof bmp ;
                pbmi->cx        = 32 ;
                pbmi->cy        = 32 ;
                pbmi->cPlanes   = 1 ;
                pbmi->cBitCount = 1 ;

                pbmi->argbColor[0].bBlue  = 0 ;
                pbmi->argbColor[0].bGreen = 0 ;
                pbmi->argbColor[0].bRed   = 0 ;
                pbmi->argbColor[1].bBlue  = 0xFF ;
                pbmi->argbColor[1].bGreen = 0xFF ;
                pbmi->argbColor[1].bRed   = 0xFF ;

                GpiSetBitmapBits (hpsMemory, 0L, 32L, abBitCat, pbmi) ;

                free (pbmi) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                aptl[0].x = 0 ;                    // target lower left
                aptl[0].y = 0 ;

                aptl[1].x = cxClient ;             // target upper right
                aptl[1].y = cyClient ;

                aptl[2].x = 0 ;                    // source lower left
                aptl[2].y = 0 ;

                aptl[3].x = 32 ;                   // source upper right
                aptl[3].y = 32 ;

                GpiBitBlt (hps, hpsMemory, 4L, aptl, ROP_SRCCOPY, BBO_AND) ;

                aptl[1] = aptl[3] ;                // target upper right

                GpiBitBlt (hps, hpsMemory, 3L, aptl, ROP_SRCCOPY, BBO_AND) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                GpiDestroyPS (hpsMemory) ;
                DevCloseDC (hdcMemory) ;
                GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BITLIB.ASM
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\BITLIB.ASM

 ;---------------------------------------------------------------
 ; BITLIB.ASM assembly language module for resource-only library
 ;---------------------------------------------------------------

 end


 BLOKOUT1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\BLOKOUT1.C

 /*-----------------------------------------
    BLOKOUT1.C -- Mouse Button Demo Program
   -----------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BlokOut1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawBoxOutline (HWND hwnd, POINTL *pptlStart, POINTL *pptlEnd)
      {
      HPS hps ;

      hps = WinGetPS (hwnd) ;
      GpiSetMix (hps, FM_INVERT) ;

      GpiMove (hps, pptlStart) ;
      GpiBox (hps, DRO_OUTLINE, pptlEnd, 0L, 0L) ;

      WinReleasePS (hps) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL   fButtonDown, fValidBox ;
      static POINTL ptlStart, ptlEnd, ptlBoxStart, ptlBoxEnd ;
      HPS           hps ;

      switch (msg)
           {
           case WM_BUTTON1DOWN:
                ptlStart.x = ptlEnd.x = MOUSEMSG(&msg)->x ;
                ptlStart.y = ptlEnd.y = MOUSEMSG(&msg)->y ;

                DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                fButtonDown = TRUE ;
                break ;                       // do default processing

           case WM_MOUSEMOVE:
                if (fButtonDown)
                     {
                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                     ptlEnd.x = MOUSEMSG(&msg)->x ;
                     ptlEnd.y = MOUSEMSG(&msg)->y ;

                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
                     }
                break ;                       // do default processing

           case WM_BUTTON1UP:
                if (fButtonDown)
                     {
                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                     ptlBoxStart = ptlStart ;
                     ptlBoxEnd.x = MOUSEMSG(&msg)->x ;
                     ptlBoxEnd.y = MOUSEMSG(&msg)->y ;

                     fButtonDown = FALSE ;
                     fValidBox = TRUE ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (fValidBox)
                     {
                     GpiMove (hps, &ptlBoxStart) ;
                     GpiBox (hps, DRO_OUTLINEFILL, &ptlBoxEnd, 0L, 0L) ;
                     }
                if (fButtonDown)
                     {
                     GpiSetMix (hps, FM_INVERT) ;

                     GpiMove (hps, &ptlStart) ;
                     GpiBox (hps, DRO_OUTLINE, &ptlEnd, 0L, 0L) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BLOKOUT2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\BLOKOUT2.C

 /*---------------------------------------------------
    BLOKOUT2.C -- Mouse Button & Capture Demo Program
   ---------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "BlokOut2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawBoxOutline (HWND hwnd, POINTL *pptlStart, POINTL *pptlEnd)
      {
      HPS hps ;

      hps = WinGetPS (hwnd) ;
      GpiSetMix (hps, FM_INVERT) ;

      GpiMove (hps, pptlStart) ;
      GpiBox (hps, DRO_OUTLINE, pptlEnd, 0L, 0L) ;

      WinReleasePS (hps) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL   fCapture, fValidBox ;
      static POINTL ptlStart, ptlEnd, ptlBoxStart, ptlBoxEnd ;
      HPS           hps ;

      switch (msg)
           {
           case WM_BUTTON1DOWN:
                ptlStart.x = ptlEnd.x = MOUSEMSG(&msg)->x ;
                ptlStart.y = ptlEnd.y = MOUSEMSG(&msg)->y ;

                DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                WinSetCapture (HWND_DESKTOP, hwnd) ;
                fCapture = TRUE ;
                break ;                       // do default processing

           case WM_MOUSEMOVE:
                if (fCapture)
                     {
                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                     ptlEnd.x = MOUSEMSG(&msg)->x ;
                     ptlEnd.y = MOUSEMSG(&msg)->y ;

                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
                     }
                break ;                       // do default processing

           case WM_BUTTON1UP:
                if (fCapture)
                     {
                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                     ptlBoxStart = ptlStart ;
                     ptlBoxEnd.x = MOUSEMSG(&msg)->x ;
                     ptlBoxEnd.y = MOUSEMSG(&msg)->y ;

                     WinSetCapture (HWND_DESKTOP, NULL) ;
                     fCapture = FALSE ;
                     fValidBox = TRUE ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                return 0 ;

           case WM_CHAR:
                if (fCapture && CHARMSG(&msg)->fs   &  KC_VIRTUALKEY &&
                              !(CHARMSG(&msg)->fs   &  KC_KEYUP)     &&
                                CHARMSG(&msg)->vkey == VK_ESC)
                     {
                     DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;

                     WinSetCapture (HWND_DESKTOP, NULL) ;
                     fCapture = FALSE ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (fValidBox)
                     {
                     GpiMove (hps, &ptlBoxStart) ;
                     GpiBox (hps, DRO_OUTLINEFILL, &ptlBoxEnd, 0L, 0L) ;
                     }
                if (fCapture)
                     {
                     GpiSetMix (hps, FM_INVERT) ;
                     GpiMove (hps, &ptlStart) ;
                     GpiBox (hps, DRO_OUTLINE, &ptlEnd, 0L, 0L) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BLOWUP.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP15\BLOWUP.C

 /*------------------------------------
    BLOWUP.C -- Screen Capture Program
   ------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "blowup.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass [] = "BlowUp" ;
 HAB  hab ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ACCELTABLE ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 BOOL BeginTracking (RECTL *prclTrack)
      {
      LONG      cxScreen, cyScreen, cxPointer, cyPointer ;
      TRACKINFO ti ;

      cxScreen  = WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN) ;
      cyScreen  = WinQuerySysValue (HWND_DESKTOP, SV_CYSCREEN) ;
      cxPointer = WinQuerySysValue (HWND_DESKTOP, SV_CXPOINTER) ;
      cyPointer = WinQuerySysValue (HWND_DESKTOP, SV_CYPOINTER) ;

                                    // Set up track rectangle for moving

      ti.cxBorder = 1 ;                       // Border width
      ti.cyBorder = 1 ;
      ti.cxGrid = 0 ;                         // Not used
      ti.cyGrid = 0 ;
      ti.cxKeyboard = 4 ;                     // Pixel increment for keyboard
      ti.cyKeyboard = 4 ;

      ti.rclBoundary.xLeft   = 0 ;            // Area for tracking rectangle
      ti.rclBoundary.yBottom = 0 ;
      ti.rclBoundary.xRight  = cxScreen ;
      ti.rclBoundary.yTop    = cyScreen ;

      ti.ptlMinTrackSize.x = 1 ;              // Minimum rectangle size
      ti.ptlMinTrackSize.y = 1 ;

      ti.ptlMaxTrackSize.x = cxScreen ;       // Maximum rectangle size
      ti.ptlMaxTrackSize.y = cyScreen ;
                                              // Initial position

      ti.rclTrack.xLeft   = (cxScreen - cxPointer) / 2 ;
      ti.rclTrack.yBottom = (cyScreen - cyPointer) / 2 ;
      ti.rclTrack.xRight  = (cxScreen + cxPointer) / 2 ;
      ti.rclTrack.yTop    = (cyScreen + cyPointer) / 2 ;

      ti.fs = TF_MOVE | TF_STANDARD | TF_SETPOINTERPOS ;     // Flags

      if (!WinTrackRect (HWND_DESKTOP, NULL, &ti))
           return FALSE ;
                                    // Switch to "sizing" pointer
      WinSetPointer (HWND_DESKTOP,
                WinQuerySysPointer (HWND_DESKTOP, SPTR_SIZENESW, FALSE)) ;

                                    // Track rectangle for sizing

      ti.fs = TF_RIGHT | TF_TOP | TF_STANDARD | TF_SETPOINTERPOS ;

      if (!WinTrackRect (HWND_DESKTOP, NULL, &ti))
           return FALSE ;

      *prclTrack = ti.rclTrack ;    // Final rectangle

      return TRUE ;
      }

 HBITMAP CopyScreenToBitmap (RECTL *prclTrack)
      {
      BITMAPINFOHEADER bmp ;
      HBITMAP          hbm ;
      HDC              hdcMemory ;
      HPS              hps, hpsMemory ;
      LONG             alBmpFormats[2] ;
      POINTL           aptl[3] ;
      SIZEL            sizl ;
                                    // Create memory DC and PS

      hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                               PU_PELS    | GPIF_DEFAULT |
                               GPIT_MICRO | GPIA_ASSOC) ;

                                    // Create bitmap for destination

      GpiQueryDeviceBitmapFormats (hpsMemory, 2L, alBmpFormats) ;

      bmp.cbFix     = sizeof bmp ;
      bmp.cx        = (USHORT) (prclTrack->xRight - prclTrack->xLeft) ;
      bmp.cy        = (USHORT) (prclTrack->yTop   - prclTrack->yBottom) ;
      bmp.cPlanes   = (USHORT) alBmpFormats[0] ;
      bmp.cBitCount = (USHORT) alBmpFormats[1] ;

      hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, NULL, NULL) ;

                                    // Copy from screen to bitmap
      if (hbm != NULL)
           {
           GpiSetBitmap (hpsMemory, hbm) ;
           hps = WinGetScreenPS (HWND_DESKTOP) ;

           aptl[0].x = 0 ;
           aptl[0].y = 0 ;
           aptl[1].x = bmp.cx ;
           aptl[1].y = bmp.cy ;
           aptl[2].x = prclTrack->xLeft ;
           aptl[2].y = prclTrack->yBottom ;

           WinLockVisRegions (HWND_DESKTOP, TRUE) ;

           GpiBitBlt (hpsMemory, hps, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE);

           WinLockVisRegions (HWND_DESKTOP, FALSE) ;

           WinReleasePS (hps) ;
           }
                                    // Clean up
      GpiDestroyPS (hpsMemory) ;
      DevCloseDC (hdcMemory) ;

      return hbm ;
      }

 HBITMAP CopyBitmap (HBITMAP hbmSrc)
      {
      BITMAPINFOHEADER bmp ;
      HBITMAP          hbmDst ;
      HDC              hdcSrc, hdcDst ;
      HPS              hpsSrc, hpsDst ;
      POINTL           aptl[3] ;
      SIZEL            sizl ;

                                    // Create memory DC's and PS's

      hdcSrc = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;
      hdcDst = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

      sizl.cx = sizl.cy = 0 ;
      hpsSrc = GpiCreatePS (hab, hdcSrc, &sizl, PU_PELS    | GPIF_DEFAULT |
                                                GPIT_MICRO | GPIA_ASSOC) ;

      hpsDst = GpiCreatePS (hab, hdcDst, &sizl, PU_PELS    | GPIF_DEFAULT |
                                                GPIT_MICRO | GPIA_ASSOC) ;

                                    // Create bitmap

      GpiQueryBitmapParameters (hbmSrc, &bmp) ;
      hbmDst = GpiCreateBitmap (hpsDst, &bmp, 0L, NULL, NULL) ;

                                    // Copy from source to destination

      if (hbmDst != NULL)
           {
           GpiSetBitmap (hpsSrc, hbmSrc) ;
           GpiSetBitmap (hpsDst, hbmDst) ;

           aptl[0].x = aptl[0].y = 0 ;
           aptl[1].x = bmp.cx ;
           aptl[1].y = bmp.cy ;
           aptl[2]   = aptl[0] ;

           GpiBitBlt (hpsDst, hpsSrc, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE) ;
           }
                                    // Clean up
      GpiDestroyPS (hpsSrc) ;
      GpiDestroyPS (hpsDst) ;
      DevCloseDC (hdcSrc) ;
      DevCloseDC (hdcDst) ;

      return hbmDst ;
      }

 VOID BitmapCreationError (HWND hwnd)
      {
      WinMessageBox (HWND_DESKTOP, hwnd, "Cannot create bitmap.",
                     szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HBITMAP   hbm ;
      static HWND      hwndMenu ;
      static SHORT     sDisplay = IDM_ACTUAL ;
      HBITMAP          hbmClip ;
      HPS              hps ;
      RECTL            rclTrack, rclClient ;
      USHORT           usfInfo ;

      switch (msg)
           {
           case WM_CREATE:
                hwndMenu = WinWindowFromID (
                                WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                FID_MENU) ;
                return 0 ;

           case WM_INITMENU:
                switch (SHORT1FROMMP (mp1))
                     {
                     case IDM_EDIT:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_COPY, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                                           hbm != NULL ? 0 : MIA_DISABLED)) ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_PASTE, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                               WinQueryClipbrdFmtInfo (hab, CF_BITMAP, &usfInf
                                         ? 0 : MIA_DISABLED)) ;
                          return 0 ;
                     }
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_COPY:
                                         // Make copy of stored bitmap

                          hbmClip = CopyBitmap (hbm) ;

                                         // Set clipboard data to copy of bitm

                          if (hbmClip != NULL)
                               {
                               WinOpenClipbrd (hab) ;
                               WinEmptyClipbrd (hab) ;
                               WinSetClipbrdData (hab, (ULONG) hbmClip,
                                                  CF_BITMAP, CFI_HANDLE) ;
                               WinCloseClipbrd (hab) ;
                               }
                          else
                               BitmapCreationError (hwnd) ;
                          return 0 ;

                     case IDM_PASTE:
                                          // Get bitmap from clipboard

                          WinOpenClipbrd (hab) ;
                          hbmClip = WinQueryClipbrdData (hab, CF_BITMAP) ;

                          if (hbmClip != NULL)
                               {
                               if (hbm != NULL)
                                    GpiDeleteBitmap (hbm) ;

                                         // Make copy of it

                               hbm = CopyBitmap (hbmClip) ;

                               if (hbm == NULL)
                                    BitmapCreationError (hwnd) ;
                               }
                          WinCloseClipbrd (hab) ;
                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;

                     case IDM_CAPTURE:
                          if (BeginTracking (&rclTrack))
                               {
                               if (hbm != NULL)
                                    GpiDeleteBitmap (hbm) ;

                               hbm = CopyScreenToBitmap (&rclTrack) ;

                               if (hbm == NULL)
                                    BitmapCreationError (hwnd) ;

                               WinInvalidateRect (hwnd, NULL, FALSE) ;
                               }
                          return 0 ;

                     case IDM_ACTUAL:
                     case IDM_STRETCH:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sDisplay, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, 0)) ;

                          sDisplay = COMMANDMSG(&msg)->cmd ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sDisplay, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED))

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;
                     }

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (hbm != NULL)
                     {
                     WinQueryWindowRect (hwnd, &rclClient) ;

                     WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rclClient,
                                    CLR_NEUTRAL, CLR_BACKGROUND,
                                    sDisplay == IDM_STRETCH ?
                                         DBM_STRETCH : DBM_NORMAL) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (hbm != NULL)
                     GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BRICKS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\BRICKS.C

 /*--------------------------------------------
    BRICKS.C -- Customized Pattern from Bitmap
   --------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>

 #define LCID_BRICKS_BITMAP    1L

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Bricks" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BYTE      abBrick [] = {
                                    0x00, 0x00, 0x00, 0x00,
                                    0xF3, 0x00, 0x00, 0x00,
                                    0xF3, 0x00, 0x00, 0x00,
                                    0xF3, 0x00, 0x00, 0x00,
                                    0x00, 0x00, 0x00, 0x00,
                                    0x3F, 0x00, 0x00, 0x00,
                                    0x3F, 0x00, 0x00, 0x00,
                                    0x3F, 0x00, 0x00, 0x00
                                    } ;
      static HBITMAP   hbm ;
      static POINTL    aptl [2] ;
      BITMAPINFO       *pbmi ;
      BITMAPINFOHEADER bmp ;
      HPS              hps ;

      switch (msg)
           {
           case WM_CREATE:
                               /*----------------------
                                  Create 8 by 8 bitmap
                                 ----------------------*/

                bmp.cbFix     = sizeof bmp ;
                bmp.cx        = 8 ;
                bmp.cy        = 8 ;
                bmp.cPlanes   = 1 ;
                bmp.cBitCount = 1 ;

                pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;

                pbmi->cbFix     = sizeof bmp ;
                pbmi->cx        = 8 ;
                pbmi->cy        = 8 ;
                pbmi->cPlanes   = 1 ;
                pbmi->cBitCount = 1 ;

                pbmi->argbColor[0].bBlue  = 0 ;
                pbmi->argbColor[0].bGreen = 0 ;
                pbmi->argbColor[0].bRed   = 0 ;
                pbmi->argbColor[1].bBlue  = 0xFF ;
                pbmi->argbColor[1].bGreen = 0xFF ;
                pbmi->argbColor[1].bRed   = 0xFF ;

                hps = WinGetPS (hwnd) ;
                hbm = GpiCreateBitmap (hps, &bmp, CBM_INIT, abBrick, pbmi) ;

                WinReleasePS (hps) ;
                free (pbmi) ;
                return 0 ;

           case WM_SIZE:
                aptl[1].x = SHORT1FROMMP (mp2) ;
                aptl[1].y = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                GpiSetBitmapId (hps, hbm, LCID_BRICKS_BITMAP) ;
                GpiSetPatternSet (hps, LCID_BRICKS_BITMAP) ;

                GpiBitBlt (hps, NULL, 2L, aptl, ROP_PATCOPY, BBO_AND) ;

                GpiSetPatternSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_BRICKS_BITMAP) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BUTTONS1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP11\BUTTONS1.C

 /*-----------------------------------------
    BUTTONS1.C -- Push Button Demonstration
   -----------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "Buttons1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  *szButtonLabel[] = { "Smaller", "Larger" } ;
      static HWND  hwndFrame, hwndButton[2] ;
      static SHORT cxClient, cyClient, cxChar, cyChar ;
      FONTMETRICS  fm ;
      HPS          hps ;
      SHORT        id ;
      RECTL        rcl ;

      switch (msg)
           {
           case WM_CREATE :
                hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;

                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;

                for (id = 0 ; id < 2 ; id++)
                     hwndButton[id] = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_BUTTON,          // Class
                                         szButtonLabel[id],  // Text
                                         WS_VISIBLE |        // Style
                                              BS_PUSHBUTTON,
                                         0, 0,               // Position
                                         12 * cxChar,        // Width
                                         2 * cyChar,         // Height
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         id,                 // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params
                return 0 ;

           case WM_SIZE :
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                for (id = 0 ; id < 2 ; id++)
                     WinSetWindowPos (hwndButton[id], NULL,
                               cxClient / 2 + (14 * id - 13) * cxChar,
                               (cyClient - 2 * cyChar) / 2,
                               0, 0, SWP_MOVE) ;
                return 0 ;

           case WM_COMMAND:
                WinQueryWindowRect (hwnd, &rcl) ;
                WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL) &rcl, 2) ;

                switch (COMMANDMSG(&msg)->cmd)               // Child ID
                     {
                     case 0:                                 // "Smaller"
                          rcl.xLeft   += cxClient / 20 ;
                          rcl.xRight  -= cxClient / 20 ;
                          rcl.yBottom += cyClient / 20 ;
                          rcl.yTop    -= cyClient / 20 ;
                          break ;

                     case 1:                                 // "Larger"
                          rcl.xLeft   -= cxClient / 20 ;
                          rcl.xRight  += cxClient / 20 ;
                          rcl.yBottom -= cyClient / 20 ;
                          rcl.yTop    += cyClient / 20 ;
                          break ;
                     }
                WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;

                WinSetWindowPos (hwndFrame, NULL,
                                 (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
                                 (SHORT) rcl.xRight - (SHORT) rcl.xLeft,
                                 (SHORT) rcl.yTop   - (SHORT) rcl.yBottom,
                                 SWP_MOVE | SWP_SIZE) ;
                return 0 ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BUTTONS2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP11\BUTTONS2.C

 /*-------------------------------------------
    BUTTONS2.C -- Square Button Demonstration
   -------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 BOOL RegisterSqBtnClass (HAB) ;         // In SQBTN.C

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass[] = "Buttons2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  szSqBtnClass[] = "SqBtn",
                   *szButtonLabel[] = { "Smaller", "Larger" } ;
      static HWND  hwndFrame, hwndButton[2] ;
      static SHORT cxClient, cyClient, cxChar, cyChar ;
      FONTMETRICS  fm ;
      HPS          hps ;
      SHORT        id ;
      RECTL        rcl ;

      switch (msg)
           {
           case WM_CREATE :
                hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;

                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;

                RegisterSqBtnClass (hab) ;

                for (id = 0 ; id < 2 ; id++)
                     hwndButton[id] = WinCreateWindow (
                                         hwnd,               // Parent
                                         "SqBtn",            // Class
                                         szButtonLabel[id],  // Text
                                         WS_VISIBLE,         // Style
                                         0, 0,               // Position
                                         12 * cxChar,        // Width
                                         2 * cyChar,         // Height
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         id,                 // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params
                return 0 ;

           case WM_SIZE :
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                for (id = 0 ; id < 2 ; id++)
                     WinSetWindowPos (hwndButton[id], NULL,
                               cxClient / 2 + (14 * id - 13) * cxChar,
                               (cyClient - 2 * cyChar) / 2,
                               0, 0, SWP_MOVE) ;
                return 0 ;

           case WM_COMMAND:
                WinQueryWindowRect (hwnd, &rcl) ;
                WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL) &rcl, 2) ;

                switch (COMMANDMSG(&msg)->cmd)               // Child ID
                     {
                     case 0:                                 // "Smaller"
                          rcl.xLeft   += cxClient / 20 ;
                          rcl.xRight  -= cxClient / 20 ;
                          rcl.yBottom += cyClient / 20 ;
                          rcl.yTop    -= cyClient / 20 ;
                          break ;

                     case 1:                                 // "Larger"
                          rcl.xLeft   -= cxClient / 20 ;
                          rcl.xRight  += cxClient / 20 ;
                          rcl.yBottom -= cyClient / 20 ;
                          rcl.yTop    += cyClient / 20 ;
                          break ;
                     }
                WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;

                WinSetWindowPos (hwndFrame, NULL,
                                 (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
                                 (SHORT) rcl.xRight - (SHORT) rcl.xLeft,
                                 (SHORT) rcl.yTop   - (SHORT) rcl.yBottom,
                                 SWP_MOVE | SWP_SIZE) ;
                return 0 ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 BUTTONS3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\BUTTONS3.C

 /*----------------------------------------------------
    BUTTONS3.C -- Square Button Demonstration with DLL
   ----------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "sqbtnlib.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass[] = "Buttons3" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  szSqBtnClass [] = "SqBtn",
                   *szButtonLabel [] = { "Smaller", "Larger" } ;
      static HWND  hwndFrame, hwndButton [2] ;
      static SHORT cxClient, cyClient, cxChar, cyChar ;
      FONTMETRICS  fm ;
      HPS          hps ;
      SHORT        id ;
      RECTL        rcl ;

      switch (msg)
           {
           case WM_CREATE :
                hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;

                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;

                RegisterSqBtnClass (hab) ;

                for (id = 0 ; id < 2 ; id++)
                     hwndButton [id] = WinCreateWindow (
                                         hwnd,               // Parent
                                         "SqBtn",            // Class
                                         szButtonLabel [id], // Text
                                         WS_VISIBLE,         // Style
                                         0, 0,               // Position
                                         12 * cxChar,        // Width
                                         2 * cyChar,         // Height
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         id,                 // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params
                return 0 ;

           case WM_SIZE :
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                for (id = 0 ; id < 2 ; id++)
                     WinSetWindowPos (hwndButton [id], NULL,
                               cxClient / 2 + (14 * id - 13) * cxChar,
                               (cyClient - 2 * cyChar) / 2,
                               0, 0, SWP_MOVE) ;
                return 0 ;

           case WM_COMMAND:
                WinQueryWindowRect (hwnd, &rcl) ;
                WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL) &rcl, 2) ;

                switch (COMMANDMSG(&msg)->cmd)               // Child ID
                     {
                     case 0:                                 // "Smaller"
                          rcl.xLeft   += cxClient / 20 ;
                          rcl.xRight  -= cxClient / 20 ;
                          rcl.yBottom += cyClient / 20 ;
                          rcl.yTop    -= cyClient / 20 ;
                          break ;

                     case 1:                                 // "Larger"
                          rcl.xLeft   -= cxClient / 20 ;
                          rcl.xRight  += cxClient / 20 ;
                          rcl.yBottom -= cyClient / 20 ;
                          rcl.yTop    += cyClient / 20 ;
                          break ;
                     }
                WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;

                WinSetWindowPos (hwndFrame, NULL,
                                 (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
                                 (SHORT) rcl.xRight - (SHORT) rcl.xLeft,
                                 (SHORT) rcl.yTop   - (SHORT) rcl.yBottom,
                                 SWP_MOVE | SWP_SIZE) ;
                return 0 ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 CHECKER1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\CHECKER1.C

 /*-------------------------------------------
    CHECKER1.C -- Mouse Hit-Test Demo Program
   -------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 #define DIVISIONS 5

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Checker1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
      {
      POINTL ptl ;

      ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
      ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fBlockState [DIVISIONS] [DIVISIONS] ;
      static SHORT xBlock, yBlock ;
      HPS          hps ;
      RECTL        rcl ;
      SHORT        x, y ;

      switch (msg)
           {
           case WM_SIZE:
                xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
                yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
                return 0 ;

           case WM_BUTTON1DOWN:
           case WM_BUTTON1DBLCLK:
                if (xBlock > 0 && yBlock > 0)
                     {
                     x = MOUSEMSG(&msg)->x / xBlock ;
                     y = MOUSEMSG(&msg)->y / yBlock ;

                     if (x < DIVISIONS && y < DIVISIONS)
                          {
                          fBlockState [x][y] = !fBlockState [x][y] ;

                          rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock) ;
                          rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock) ;

                          WinInvalidateRect (hwnd, &rcl, FALSE) ;
                          }
                     else
                          WinAlarm (HWND_DESKTOP, WA_WARNING) ;
                     }
                else
                     WinAlarm (HWND_DESKTOP, WA_WARNING) ;

                break ;                       // do default processing

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (xBlock > 0 && yBlock > 0)
                     for (x = 0 ; x < DIVISIONS ; x++)
                          for (y = 0 ; y < DIVISIONS ; y++)
                               {
                               rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock
                               rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock

                               WinDrawBorder (hps, &rcl, 1, 1,
                                              CLR_NEUTRAL, CLR_BACKGROUND,
                                              DB_STANDARD | DB_INTERIOR) ;

                               if (fBlockState [x][y])
                                    {
                                    DrawLine (hps, rcl.xLeft,  rcl.yBottom,
                                                   rcl.xRight, rcl.yTop) ;

                                    DrawLine (hps, rcl.xLeft,  rcl.yTop,
                                                   rcl.xRight, rcl.yBottom) ;
                                    }
                               }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 CHECKER2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\CHECKER2.C

 /*-------------------------------------------------------------------
    CHECKER2.C -- Mouse Hit-Test Demo Program with Keyboard Interface
   -------------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include <stdlib.h>

 #define DIVISIONS 5

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Checker2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
      {
      POINTL ptl ;

      ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
      ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fBlockState [DIVISIONS] [DIVISIONS] ;
      static SHORT xBlock, yBlock ;
      HPS          hps ;
      POINTL       ptl ;
      RECTL        rcl ;
      SHORT        x, y ;

      switch (msg)
           {
           case WM_SIZE:
                xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
                yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
                return 0 ;

           case WM_BUTTON1DOWN:
           case WM_BUTTON1DBLCLK:
                if (xBlock > 0 && yBlock > 0)
                     {
                     x = MOUSEMSG(&msg)->x / xBlock ;
                     y = MOUSEMSG(&msg)->y / yBlock ;

                     if (x < DIVISIONS && y < DIVISIONS)
                          {
                          fBlockState [x][y] = !fBlockState [x][y] ;

                          rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock) ;
                          rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock) ;

                          WinInvalidateRect (hwnd, &rcl, FALSE) ;
                          }
                     else
                          WinAlarm (HWND_DESKTOP, WA_WARNING) ;
                     }
                else
                     WinAlarm (HWND_DESKTOP, WA_WARNING) ;

                break ;                       // do default processing

           case WM_SETFOCUS:
                if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT) == 0)
                     WinShowPointer (HWND_DESKTOP,
                                     SHORT1FROMMP (mp2) ? TRUE : FALSE) ;
                return 0 ;

           case WM_CHAR:
                if (xBlock == 0 || yBlock == 0)
                     break ;

                if (CHARMSG(&msg)->fs & KC_KEYUP)
                     break ;

                if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY))
                     break ;

                WinQueryPointerPos (HWND_DESKTOP, &ptl) ;
                WinMapWindowPoints (HWND_DESKTOP, hwnd, &ptl, 1) ;

                x = max (0, min (DIVISIONS - 1, (SHORT) ptl.x / xBlock)) ;
                y = max (0, min (DIVISIONS - 1, (SHORT) ptl.y / yBlock)) ;

                switch (CHARMSG(&msg)->vkey)
                     {
                     case VK_LEFT:
                          x-- ;
                          break ;

                     case VK_RIGHT:
                          x++ ;
                          break ;

                     case VK_DOWN:
                          y-- ;
                          break ;

                     case VK_UP:
                          y++ ;
                          break ;

                     case VK_HOME:
                          x = 0 ;
                          y = DIVISIONS - 1 ;
                          break ;

                     case VK_END:
                          x = DIVISIONS - 1 ;
                          y = 0 ;
                          break ;

                     case VK_NEWLINE:
                     case VK_ENTER:
                     case VK_SPACE:
                          WinSendMsg (hwnd, WM_BUTTON1DOWN,
                               MPFROM2SHORT (x * xBlock, y * yBlock), NULL) ;
                          break ;

                     default:
                          return 0 ;
                     }
                x = (x + DIVISIONS) % DIVISIONS ;
                y = (y + DIVISIONS) % DIVISIONS ;

                ptl.x = x * xBlock + xBlock / 2 ;
                ptl.y = y * yBlock + yBlock / 2 ;

                WinMapWindowPoints (hwnd, HWND_DESKTOP, &ptl, 1) ;
                WinSetPointerPos (HWND_DESKTOP, (SHORT) ptl.x, (SHORT) ptl.y)
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (xBlock > 0 && yBlock > 0)
                     for (x = 0 ; x < DIVISIONS ; x++)
                          for (y = 0 ; y < DIVISIONS ; y++)
                               {
                               rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock
                               rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock

                               WinDrawBorder (hps, &rcl, 1, 1,
                                              CLR_NEUTRAL, CLR_BACKGROUND,
                                              DB_STANDARD | DB_INTERIOR) ;

                               if (fBlockState [x][y])
                                    {
                                    DrawLine (hps, rcl.xLeft,  rcl.yBottom,
                                                   rcl.xRight, rcl.yTop) ;

                                    DrawLine (hps, rcl.xLeft,  rcl.yTop,
                                                   rcl.xRight, rcl.yBottom) ;
                                    }
                               }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 CHECKER3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\CHECKER3.C

 /*--------------------------------------------------------------
    CHECKER3.C -- Mouse Hit-Test Demo Program with Child Windows
   --------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 #define DIVISIONS 5

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY ChildWndProc  (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Checker3" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
      {
      POINTL ptl ;

      ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
      ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR szChildClass [] = "Checker3.Child" ;
      static HWND hwndChild [DIVISIONS][DIVISIONS] ;
      SHORT       xBlock, yBlock, x, y ;

      switch (msg)
           {
           case WM_CREATE:
                WinRegisterClass (hab, szChildClass, ChildWndProc,
                                  CS_SIZEREDRAW, sizeof (USHORT)) ;

                for (x = 0 ; x < DIVISIONS ; x++)
                     for (y = 0 ; y < DIVISIONS ; y++)

                          hwndChild [x][y] =
                               WinCreateWindow (
                                         hwnd,          // Parent window
                                         szChildClass,  // Window class
                                         NULL,          // Window text
                                         WS_VISIBLE,    // Window style
                                         0, 0, 0, 0,    // Position & size
                                         hwnd,          // Owner window
                                         HWND_BOTTOM,   // Placement
                                         y << 8 | x,    // Child window ID
                                         NULL,          // Control data
                                         NULL) ;        // Pres. Params
                return 0 ;

           case WM_SIZE:
                xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
                yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;

                for (x = 0 ; x < DIVISIONS ; x++)
                     for (y = 0 ; y < DIVISIONS ; y++)

                          WinSetWindowPos (hwndChild [x][y], NULL,
                               x * xBlock, y * yBlock, xBlock, yBlock,
                               SWP_MOVE | SWP_SIZE) ;
                return 0 ;

           case WM_BUTTON1DOWN:
           case WM_BUTTON1DBLCLK:
                WinAlarm (HWND_DESKTOP, WA_WARNING) ;
                break ;                       // do default processing

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY ChildWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      HPS   hps ;
      RECTL rcl ;

      switch (msg)
           {
           case WM_CREATE:
                WinSetWindowUShort (hwnd, 0, 0) ;
                return 0 ;

           case WM_BUTTON1DOWN:
           case WM_BUTTON1DBLCLK:
                WinSetActiveWindow (HWND_DESKTOP, hwnd) ;
                WinSetWindowUShort (hwnd, 0, !WinQueryWindowUShort (hwnd, 0))
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawBorder (hps, &rcl, 1, 1, CLR_NEUTRAL, CLR_BACKGROUND,
                                    DB_STANDARD | DB_INTERIOR) ;

                if (WinQueryWindowUShort (hwnd, 0))
                     {
                     DrawLine (hps, rcl.xLeft,  rcl.yBottom,
                                    rcl.xRight, rcl.yTop) ;
                     DrawLine (hps, rcl.xLeft,  rcl.yTop,
                                    rcl.xRight, rcl.yBottom) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 CLOCK.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP10\CLOCK.C

 /*-------------------------
    CLOCK.C -- Analog Clock
   -------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>

 #define ID_TIMER 1

 typedef struct
      {
      SHORT cxClient ;
      SHORT cyClient ;
      SHORT cxPixelDiam ;
      SHORT cyPixelDiam ;
      }
      WINDOWINFO ;

 typedef WINDOWINFO *PWINDOWINFO ;

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "Clock" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
           {
           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinStopTimer (hab, hwndClient, ID_TIMER) ;
           }
      else
           WinMessageBox (HWND_DESKTOP, hwndClient,
                          "Too many clocks or timers",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID RotatePoint (POINTL aptl[], SHORT sNum, SHORT sAngle)
      {
      static SHORT sSin [60] =
                     {
                        0,  105,  208,  309,  407,  500,  588,  669,  743,  80
                      866,  914,  951,  978,  995, 1000,  995,  978,  951,  91
                      866,  809,  743,  669,  588,  500,  407,  309,  208,  10
                        0, -104, -207, -308, -406, -499, -587, -668, -742, -80
                     -865, -913, -950, -977, -994, -999, -994, -977, -950, -91
                     -865, -808, -742, -668, -587, -499, -406, -308, -207, -10
                     } ;
      POINTL       ptlTemp ;
      SHORT        sIndex ;

      for (sIndex = 0 ; sIndex < sNum ; sIndex++)
           {
           ptlTemp.x = (aptl[sIndex].x * sSin [(sAngle + 15) % 60] +
                        aptl[sIndex].y * sSin [sAngle]) / 1000 ;

           ptlTemp.y = (aptl[sIndex].y * sSin [(sAngle + 15) % 60] -
                        aptl[sIndex].x * sSin [sAngle]) / 1000 ;

           aptl[sIndex] = ptlTemp ;
           }
      }

 VOID ScalePoint (POINTL aptl[], SHORT sNum, PWINDOWINFO pwi)
      {
      SHORT sIndex ;

      for (sIndex = 0 ; sIndex < sNum ; sIndex++)
           {
           aptl[sIndex].x = aptl[sIndex].x * pwi->cxPixelDiam / 200 ;
           aptl[sIndex].y = aptl[sIndex].y * pwi->cyPixelDiam / 200 ;
           }
      }

 VOID TranslatePoint (POINTL aptl[], SHORT sNum, PWINDOWINFO pwi)
      {
      SHORT sIndex ;

      for (sIndex = 0 ; sIndex < sNum ; sIndex++)
           {
           aptl[sIndex].x += pwi->cxClient / 2 ;
           aptl[sIndex].y += pwi->cyClient / 2 ;
           }
      }

 VOID DrawHand (HPS hps, POINTL aptlIn[], SHORT sNum, SHORT sAngle,
                PWINDOWINFO pwi)
      {
      POINTL aptl [5] ;
      SHORT  sIndex ;

      for (sIndex = 0 ; sIndex < sNum ; sIndex++)
           aptl [sIndex] = aptlIn [sIndex] ;

      RotatePoint    (aptl, sNum, sAngle) ;
      ScalePoint     (aptl, sNum, pwi) ;
      TranslatePoint (aptl, sNum, pwi) ;

      GpiMove (hps, aptl) ;
      GpiPolyLine (hps, sNum - 1L, aptl + 1) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static DATETIME   dtPrevious ;
      static HDC        hdc ;
      static LONG       xPixelsPerMeter, yPixelsPerMeter ;
      static POINTL     aptlHour   [5] = { 0,-15, 10,0, 0,60, -10,0, 0,-15 },
                        aptlMinute [5] = { 0,-20,  5,0, 0,80,  -5,0, 0,-20 },
                        aptlSecond [2] = { 0,  0,  0,80 } ;
      static WINDOWINFO wi ;
      DATETIME          dt ;
      HPS               hps ;
      POINTL            aptl [3] ;
      SHORT             sDiamMM, sAngle ;

      switch (msg)
           {
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,
                                   1L, &yPixelsPerMeter) ;
                DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION,
                                   1L, &xPixelsPerMeter) ;

                DosGetDateTime (&dtPrevious) ;
                dtPrevious.hours = (dtPrevious.hours * 5) % 60 +
                                    dtPrevious.minutes / 12 ;
                return 0 ;

           case WM_SIZE:
                wi.cxClient = SHORT1FROMMP (mp2) ;
                wi.cyClient = SHORT2FROMMP (mp2) ;

                sDiamMM = (SHORT) min (wi.cxClient * 1000L / xPixelsPerMeter,
                                       wi.cyClient * 1000L / yPixelsPerMeter)

                wi.cxPixelDiam = (SHORT) (xPixelsPerMeter * sDiamMM / 1000) ;
                wi.cyPixelDiam = (SHORT) (yPixelsPerMeter * sDiamMM / 1000) ;
                return 0 ;

           case WM_TIMER:
                DosGetDateTime (&dt) ;
                dt.hours = (dt.hours * 5) % 60 + dt.minutes / 12 ;

                hps = WinGetPS (hwnd) ;
                GpiSetColor (hps, CLR_BACKGROUND) ;

                DrawHand (hps, aptlSecond, 2, dtPrevious.seconds, &wi) ;

                if (dt.hours   != dtPrevious.hours ||
                    dt.minutes != dtPrevious.minutes)
                     {
                     DrawHand (hps, aptlHour,   5, dtPrevious.hours,   &wi) ;
                     DrawHand (hps, aptlMinute, 5, dtPrevious.minutes, &wi) ;
                     }

                GpiSetColor (hps, CLR_NEUTRAL) ;

                DrawHand (hps, aptlHour,   5, dt.hours,   &wi) ;
                DrawHand (hps, aptlMinute, 5, dt.minutes, &wi) ;
                DrawHand (hps, aptlSecond, 2, dt.seconds, &wi) ;

                WinReleasePS (hps) ;
                dtPrevious = dt ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sAngle = 0 ; sAngle < 60 ; sAngle++)
                     {
                     aptl[0].x = 0 ;
                     aptl[0].y = 90 ;

                     RotatePoint    (aptl, 1, sAngle) ;
                     ScalePoint     (aptl, 1, &wi) ;
                     TranslatePoint (aptl, 1, &wi) ;

                     aptl[2].x = aptl[2].y = sAngle % 5 ? 2 : 10 ;

                     ScalePoint (aptl + 2, 1, &wi) ;

                     aptl[0].x -= aptl[2].x / 2 ;
                     aptl[0].y -= aptl[2].y / 2 ;

                     aptl[1].x = aptl[0].x + aptl[2].x ;
                     aptl[1].y = aptl[0].y + aptl[2].y ;

                     GpiMove (hps, aptl) ;
                     GpiBox (hps, DRO_OUTLINEFILL, aptl + 1,
                                  aptl[2].x, aptl[2].y) ;
                     }
                DrawHand (hps, aptlHour,   5, dtPrevious.hours,   &wi) ;
                DrawHand (hps, aptlMinute, 5, dtPrevious.minutes, &wi) ;
                DrawHand (hps, aptlSecond, 2, dtPrevious.seconds, &wi) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 COLORS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\COLORS.C

 /*-----------------------------------
    COLORS.C -- GPI Foreground Colors
   -----------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <string.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Colors" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static struct {
                    LONG lColorIndex ;
                    CHAR *szColorIndex ;
                    }
                    show [] =
                    {
                    CLR_FALSE      , "CLR_FALSE"      ,
                    CLR_TRUE       , "CLR_TRUE"       ,
                    CLR_DEFAULT    , "CLR_DEFAULT"    ,
                    CLR_WHITE      , "CLR_WHITE"      ,
                    CLR_BLACK      , "CLR_BLACK"      ,
                    CLR_BACKGROUND , "CLR_BACKGROUND" ,
                    CLR_BLUE       , "CLR_BLUE"       ,
                    CLR_RED        , "CLR_RED"        ,
                    CLR_PINK       , "CLR_PINK"       ,
                    CLR_GREEN      , "CLR_GREEN"      ,
                    CLR_CYAN       , "CLR_CYAN"       ,
                    CLR_YELLOW     , "CLR_YELLOW"     ,
                    CLR_NEUTRAL    , "CLR_NEUTRAL"    ,
                    CLR_DARKGRAY   , "CLR_DARKGRAY"   ,
                    CLR_DARKBLUE   , "CLR_DARKBLUE"   ,
                    CLR_DARKRED    , "CLR_DARKRED"    ,
                    CLR_DARKPINK   , "CLR_DARKPINK"   ,
                    CLR_DARKGREEN  , "CLR_DARKGREEN"  ,
                    CLR_DARKCYAN   , "CLR_DARKCYAN"   ,
                    CLR_BROWN      , "CLR_BROWN"      ,
                    CLR_PALEGRAY   , "CLR_PALEGRAY"
                    } ;
      static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
                    sNumColors = sizeof show / sizeof show[0] ;
      FONTMETRICS   fm ;
      HPS           hps ;
      POINTL        ptl ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sIndex = 0 ; sIndex < sNumColors ; sIndex ++)
                     {
                     ptl.x = (sIndex < 11 ? 1 : 33) * cxCaps ;
                     ptl.y = cyClient - (sIndex % 11 * 5 + 4) * cyChar / 2
                                                        + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                           (LONG) strlen (show [sIndex].szColorIndex),
                                show [sIndex].szColorIndex) ;

                     ptl.x = (sIndex < 11 ? 20 : 52) * cxCaps ;
                            ptl.y -= cyDesc + cyChar / 2 ;
                     GpiMove (hps, &ptl) ;

                     GpiSavePS (hps) ;
                     GpiSetColor (hps, show [sIndex].lColorIndex) ;

                     ptl.x += 10 * cxCaps ;
                     ptl.y += 2 * cyChar ;
                     GpiBox (hps, DRO_FILL, &ptl, 0L, 0L) ;

                     GpiRestorePS (hps, -1L) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 COLORSCR.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP11\COLORSCR.C

 /*--------------------------------------------------------
    COLORSCR.C -- Color Scroll using child window controls
   --------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY ScrollProc (HWND, USHORT, MPARAM, MPARAM) ;

 HWND  hwndScroll[3], hwndFocus ;
 PFNWP pfnOldScroll[3] ;

 int main (void)
      {
      static CHAR  szClientClass[] = "ColorScr" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSetFocus (HWND_DESKTOP, hwndFocus = hwndScroll[0]) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  *szColorLabel[] = { "Red", "Green", "Blue" } ;
      static HWND  hwndLabel[3], hwndValue[3] ;
      static SHORT cyChar, sColor[3] ;
      static RECTL rclRightHalf ;
      CHAR         szBuffer[10] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      SHORT        s, id, cxClient, cyClient ;

      switch (msg)
           {
           case WM_CREATE :
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;

                for (s = 0 ; s < 3 ; s++)
                     {
                     hwndScroll[s] = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_SCROLLBAR,       // Class
                                         NULL,               // Text
                                         WS_VISIBLE |        // Style
                                              SBS_VERT,
                                         0, 0,               // Position
                                         0, 0,               // Size
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         s,                  // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                     hwndLabel[s]  = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_STATIC,          // Class
                                         szColorLabel[s],    // Text
                                         WS_VISIBLE |        // Style
                                           SS_TEXT | DT_CENTER,
                                         0, 0,               // Position
                                         0, 0,               // Size
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         s + 3,              // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                     hwndValue[s]  = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_STATIC,          // Class
                                         "0",                // Text
                                         WS_VISIBLE |        // Style
                                           SS_TEXT | DT_CENTER,
                                         0, 0,               // Position
                                         0, 0,               // Size
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         s + 6,              // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                     pfnOldScroll[s] =
                               WinSubclassWindow (hwndScroll[s], ScrollProc) ;

                     WinSendMsg (hwndScroll[s], SBM_SETSCROLLBAR,
                                 MPFROM2SHORT (0, 0), MPFROM2SHORT (0, 255)) ;
                     }
                return 0 ;

           case WM_SIZE :
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                for (s = 0 ; s < 3 ; s++)
                     {
                     WinSetWindowPos (hwndScroll[s], NULL,
                                      (2 * s + 1) * cxClient / 14, 2 * cyChar,
                                      cxClient / 14, cyClient - 4 * cyChar,
                                      SWP_SIZE | SWP_MOVE) ;

                     WinSetWindowPos (hwndLabel[s], NULL,
                                      (4 * s + 1) * cxClient / 28,
                                      cyClient - 3 * cyChar / 2,
                                      cxClient / 7, cyChar,
                                      SWP_SIZE | SWP_MOVE) ;

                     WinSetWindowPos (hwndValue[s], NULL,
                                      (4 * s + 1) * cxClient / 28, cyChar / 2,
                                      cxClient / 7, cyChar,
                                      SWP_SIZE | SWP_MOVE) ;
                     }

                WinQueryWindowRect (hwnd, &rclRightHalf) ;
                rclRightHalf.xLeft = rclRightHalf.xRight / 2 ;
                return 0 ;

           case WM_VSCROLL :
                id = SHORT1FROMMP (mp1) ;          // ID of scroll bar

                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEDOWN :
                          sColor[id] = min (255, sColor[id] + 1) ;
                          break ;

                     case SB_LINEUP :
                          sColor[id] = max (0, sColor[id] - 1) ;
                          break ;

                     case SB_PAGEDOWN :
                          sColor[id] = min (255, sColor[id] + 16) ;
                          break ;

                     case SB_PAGEUP :
                          sColor[id] = max (0, sColor[id] - 16) ;
                          break ;

                     case SB_SLIDERTRACK :
                          sColor[id] = SHORT1FROMMP (mp2) ;
                          break ;

                     default :
                          return 0 ;
                     }
                WinSendMsg (hwndScroll[id], SBM_SETPOS,
                            MPFROM2SHORT (sColor[id], 0), NULL) ;

                WinSetWindowText (hwndValue[id],
                                  itoa (sColor[id], szBuffer, 10)) ;
                WinInvalidateRect (hwnd, &rclRightHalf, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                GpiCreateLogColorTable (hps, LCOL_RESET, LCOLF_RGB,
                                             0L, 0L, NULL) ;

                WinFillRect (hps, &rclRightHalf, (ULONG) sColor[0] << 16 |
                                                 (ULONG) sColor[1] <<  8 |
                                                 (ULONG) sColor[2]) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY ScrollProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      USHORT id ;

      id = WinQueryWindowUShort (hwnd, QWS_ID) ;   // ID of scroll bar

      switch (msg)
           {
           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY))
                     break ;

                switch (CHARMSG(&msg)->vkey)
                     {
                     case VK_TAB:
                          if (!(CHARMSG(&msg)->fs & KC_KEYUP))
                               {
                               hwndFocus = hwndScroll[(id + 1) % 3] ;
                               WinSetFocus (HWND_DESKTOP, hwndFocus) ;
                               }
                          return 1 ;

                     case VK_BACKTAB:
                          if (!(CHARMSG(&msg)->fs & KC_KEYUP))
                               {
                               hwndFocus = hwndScroll[(id + 2) % 3] ;
                               WinSetFocus (HWND_DESKTOP, hwndFocus) ;
                               }
                          return 1 ;

                     default:
                          break ;
                     }
                break ;

           case WM_BUTTON1DOWN:
                WinSetFocus (HWND_DESKTOP, hwndFocus = hwnd) ;
                break ;
           }
      return pfnOldScroll[id] (hwnd, msg, mp1, mp2) ;
      }


 CONVMENU.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP13\CONVMENU.C

 /*-------------------------------------
    CONVMENU.C -- Conventional Menu Use
   -------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "convmenu.h"

 #define ID_TIMER    1

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass[] = "ConvMenu" ;
 HAB  hab ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ACCELTABLE ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (TRUE)
           {
           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           if (MBID_OK == WinMessageBox (HWND_DESKTOP, hwndClient,
                                         "Really want to end program?",
                                         szClientClass, 0,
                                         MB_OKCANCEL | MB_ICONQUESTION))
                break ;

           WinCancelShutdown (hmq, FALSE) ;
           }

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fTimerGoing = FALSE ;
      static COLOR colBackground [] = {
                                      0xFFFFFFL, 0xC0C0C0L, 0x808080L,
                                      0x404040L, 0x000000L
                                      } ;
      static HWND  hwndMenu ;
      static SHORT sCurrentBackground = IDM_WHITE ;
      HPS          hps ;
      RECTL        rcl ;

      switch (msg)
           {
           case WM_CREATE:
                hwndMenu = WinWindowFromID (
                               WinQueryWindow (hwnd, QW_PARENT, FALSE),
                               FID_MENU) ;
                return 0 ;

           case WM_INITMENU:
                switch (SHORT1FROMMP (mp1))
                     {
                     case IDM_TIMER:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                    MPFROM2SHORT (IDM_START, TRUE),
                                    MPFROM2SHORT (MIA_DISABLED,
                                              !fTimerGoing &&
                               WinQuerySysValue (HWND_DESKTOP, SV_CTIMERS) ?
                                              0 : MIA_DISABLED)) ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                    MPFROM2SHORT (IDM_STOP, TRUE),
                                    MPFROM2SHORT (MIA_DISABLED,
                                         fTimerGoing ? 0 : MIA_DISABLED)) ;
                          return 0 ;
                     }
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_NEW:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Bogus \"New\" Dialog",
                                    szClientClass, 0, MB_OK | MB_ICONASTERISK)
                          return 0 ;

                     case IDM_OPEN:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Bogus \"Open\" Dialog",
                                    szClientClass, 0, MB_OK | MB_ICONASTERISK)
                          return 0 ;

                     case IDM_SAVE:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Bogus \"Save\" Dialog",
                                    szClientClass, 0, MB_OK | MB_ICONASTERISK)
                          return 0 ;

                     case IDM_SAVEAS:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Bogus \"Save As\" Dialog",
                                    szClientClass, 0, MB_OK | MB_ICONASTERISK)
                          return 0 ;

                     case IDM_EXIT:
                          WinSendMsg (hwnd, WM_CLOSE, 0L, 0L) ;
                          return 0 ;

                     case IDM_ABOUT:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Bogus \"About\" Dialog",
                                    szClientClass, 0, MB_OK | MB_ICONASTERISK)
                          return 0 ;

                     case IDM_START:
                          if (WinStartTimer (hab, hwnd, ID_TIMER, 1000))
                               fTimerGoing = TRUE ;
                          else
                               WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Too many clocks or timers",
                                    szClientClass, 0,
                                    MB_OK | MB_ICONEXCLAMATION) ;
                          return 0 ;

                     case IDM_STOP:
                          WinStopTimer (hab, hwnd, ID_TIMER) ;
                          fTimerGoing = FALSE ;
                          return 0 ;

                     case IDM_WHITE:
                     case IDM_LTGRAY:
                     case IDM_GRAY:
                     case IDM_DKGRAY:
                     case IDM_BLACK:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sCurrentBackground, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, 0)) ;

                          sCurrentBackground = COMMANDMSG(&msg)->cmd ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sCurrentBackground, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED))

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;
                     }
                break ;

           case WM_HELP:
                WinMessageBox (HWND_DESKTOP, hwnd,
                               "Help not yet implemented",
                               szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
                return 0 ;

           case WM_TIMER:
                WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiSavePS (hps) ;

                GpiCreateLogColorTable (hps, 0L, LCOLF_RGB, 0L, 0L, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinFillRect (hps, &rcl,
                             colBackground [sCurrentBackground - IDM_WHITE]) ;

                GpiRestorePS (hps, -1L) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (fTimerGoing)
                     {
                     WinStopTimer (hab, hwnd, ID_TIMER) ;
                     fTimerGoing = FALSE ;
                     }
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 DEVCAPS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\DEVCAPS.C

 /*--------------------------------------------------
    DEVCAPS.C -- Device Capabilities Display Program
   --------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "devcaps.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "DevCaps" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText)
      {
      POINTL aptlTextBox[TXTBOX_COUNT] ;

      GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;

      pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;

      return GpiCharStringAt (hps, pptl, lLength, pchText) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HDC   hdc ;
      static SHORT cxClient, cyClient, cxCaps, cyChar, cyDesc ;
      CHAR         szBuffer [12] ;
      FONTMETRICS  fm ;
      LONG         lValue ;
      POINTL       ptl ;
      HPS          hps ;
      SHORT        sLine ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;
                WinReleasePS (hps) ;

                hdc = WinOpenWindowDC (hwnd) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sLine = 0 ; sLine < NUMLINES ; sLine++)
                     {
                     ptl.x = cxCaps ;
                     ptl.y = cyClient - cyChar * (sLine + 2) + cyDesc ;

                     if (sLine >= (NUMLINES + 1) / 2)
                          {
                          ptl.x += cxCaps * 35 ;
                          ptl.y += cyChar * (NUMLINES + 1) / 2 ;
                          }

                     DevQueryCaps (hdc, devcaps[sLine].lIndex, 1L, &lValue) ;

                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (devcaps[sLine].szIdentifier),
                               devcaps[sLine].szIdentifier) ;

                     ptl.x += 33 * cxCaps ;
                     RtJustCharStringAt (hps, &ptl,
                               (LONG) strlen (ltoa (lValue, szBuffer, 10)),
                               szBuffer) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }



 DIGCLOCK.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP10\DIGCLOCK.C

 /*-----------------------------
    DIGCLOCK.C -- Digital Clock
   -----------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOS
 #include <os2.h>
 #include <stdio.h>

 #define ID_TIMER 1

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID    SizeTheWindow (HWND) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "DigClock" ;
      static ULONG flFrameFlags = FCF_TITLEBAR | FCF_SYSMENU  |
                                  FCF_BORDER   | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;
      SizeTheWindow (hwndFrame) ;

      if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
           {
           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinStopTimer (hab, hwndClient, ID_TIMER) ;
           }
      else
           WinMessageBox (HWND_DESKTOP, hwndClient,
                          "Too many clocks or timers",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID SizeTheWindow (HWND hwndFrame)
      {
      FONTMETRICS fm ;
      HPS         hps ;
      RECTL       rcl ;

      hps = WinGetPS (hwndFrame) ;
      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
      WinReleasePS (hps) ;

      rcl.yBottom = 0 ;
      rcl.yTop    = 11 * fm.lMaxBaselineExt / 4 ;
      rcl.xRight  = WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN) ;
      rcl.xLeft   = rcl.xRight - 16 * fm.lEmInc ;

      WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;

      WinSetWindowPos (hwndFrame, NULL, (SHORT) rcl.xLeft, (SHORT) rcl.yBottom
                       (SHORT) (rcl.xRight - rcl.xLeft),
                       (SHORT) (rcl.yTop - rcl.yBottom), SWP_SIZE | SWP_MOVE)
      }

 VOID UpdateTime (HWND hwnd, HPS hps)
      {
      static BOOL        fHaveCtryInfo = FALSE ;
      static CHAR        *szDayName [] = { "Sun", "Mon", "Tue", "Wed",
                                           "Thu", "Fri", "Sat" } ;
      static CHAR        szDateFormat [] = " %s  %d%s%02d%s%02d " ;
      static COUNTRYCODE ctryc = { 0, 0 } ;
      static COUNTRYINFO ctryi ;
      CHAR               szBuffer [20] ;
      DATETIME           dt ;
      RECTL              rcl ;
      USHORT             usDataLength ;

                /*----------------------------------------
                   Get Country Information, Date and Time
                  ----------------------------------------*/

      if (!fHaveCtryInfo)
           {
           DosGetCtryInfo (sizeof ctryi, &ctryc, &ctryi, &usDataLength) ;
           fHaveCtryInfo = TRUE ;
           }
      DosGetDateTime (&dt) ;
      dt.year %= 100 ;

                /*-------------
                   Format Date
                  -------------*/
                                    /*-----------------
                                       mm/dd/yy format
                                      -----------------*/
      if (ctryi.fsDateFmt == 0)

           sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
                              dt.month, ctryi.szDateSeparator,
                              dt.day,   ctryi.szDateSeparator, dt.year) ;

                                    /*-----------------
                                       dd/mm/yy format
                                      -----------------*/
      else if (ctryi.fsDateFmt == 1)

           sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
                              dt.day,   ctryi.szDateSeparator,
                              dt.month, ctryi.szDateSeparator, dt.year) ;

                                    /*-----------------
                                       yy/mm/dd format
                                      -----------------*/
      else
           sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
                              dt.year,  ctryi.szDateSeparator,
                              dt.month, ctryi.szDateSeparator, dt.day) ;

                /*--------------
                   Display Date
                  --------------*/

      WinQueryWindowRect (hwnd, &rcl) ;
      rcl.yBottom += 5 * rcl.yTop / 11 ;
      WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
                   DT_CENTER | DT_VCENTER) ;

                /*-------------
                   Format Time
                  -------------*/
                                    /*----------------
                                       12-hour format
                                      ----------------*/
      if ((ctryi.fsTimeFmt & 1) == 0)

           sprintf (szBuffer, " %d%s%02d%s%02d %cm ",
                              (dt.hours + 11) % 12 + 1, ctryi.szTimeSeparator,
                              dt.minutes, ctryi.szTimeSeparator,
                              dt.seconds, dt.hours / 12 ? 'p' : 'a') ;

                                    /*----------------
                                       24-hour format
                                      ----------------*/
      else
           sprintf (szBuffer, " %02d%s%02d%s%02d ",
                              dt.hours,   ctryi.szTimeSeparator,
                              dt.minutes, ctryi.szTimeSeparator, dt.seconds) ;

                /*--------------
                   Display Time
                  --------------*/

      WinQueryWindowRect (hwnd, &rcl) ;
      rcl.yTop -= 5 * rcl.yTop / 11 ;
      WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
                   DT_CENTER | DT_VCENTER) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      HPS  hps;

      switch (msg)
           {
           case WM_TIMER:
                hps = WinGetPS (hwnd) ;
                GpiSetBackMix (hps, BM_OVERPAINT) ;

                UpdateTime (hwnd, hps) ;

                WinReleasePS (hps) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                UpdateTime (hwnd, hps) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 DRAWLINE.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP11\DRAWLINE.C

 /*--------------------------------------------
    DRAWLINE.C -- Draw line from radio buttons
   --------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "DrawLine" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR   *szGroupText[] = { "Color", "Type" } ;
      static CHAR   *szColorText[] = { "Background", "Blue",      "Red",
                                        "Pink",       "Green",     "Cyan",
                                        "Yellow",     "Neutral",   "Dark Gray"
                                        "Dark Blue",  "Dark Red",  "Dark Pink"
                                        "Dark Green", "Dark Cyan", "Brown",
                                        "Pale Gray" } ;
      static CHAR   *szTypeText [] = { "Dot",       "Short Dash",
                                        "Dash Dot",  "Double Dot",
                                        "Long Dash", "Dash Double Dot",
                                        "Solid",     "Invisible" } ;
      static HWND   hwndGroup[2], hwndRadioColor[8], hwndRadioType[8] ;
      static POINTL aptl[5] ;
      static SHORT  sCurrentColor = 7,   // Neutral
                    sCurrentType  = 6 ;  // Solid
      FONTMETRICS   fm ;
      HPS           hps ;
      SHORT         s, id, cxChar, cyChar ;

      switch (msg)
           {
           case WM_CREATE :
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;

                for (s = 0 ; s < 2 ; s++)

                     hwndGroup[s] = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_STATIC,          // Class
                                         szGroupText[s],     // Text
                                         WS_VISIBLE |        // Style
                                              SS_GROUPBOX,
                                         (8 + 42 * s) * cxChar,
                                         4 * cyChar,         // Position
                                         (26 + 12 * (1 - s)) *
                                              cxChar,        // Width
                                         14 * cyChar,        // Height
                                         hwnd,               // Owner
                                         HWND_TOP,           // Placement
                                         s + 24,             // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                for (s = 0 ; s < 16 ; s++)

                     hwndRadioColor[s] = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_BUTTON,          // Class
                                         szColorText[s],     // Text
                                         WS_VISIBLE |        // Style
                                              BS_RADIOBUTTON,
                                         (10 + (s > 7 ? 18 : 0))
                                              * cxChar,      // X Position
                                         (31 - 3 * (s % 8))
                                              * cyChar / 2,  // Y Position
                                         16 * cxChar,        // Width
                                         3 * cyChar / 2,     // Height
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         s,                  // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                for (s = 0 ; s < 8 ; s++)

                     hwndRadioType[s]  = WinCreateWindow (
                                         hwnd,               // Parent
                                         WC_BUTTON,          // Class
                                         szTypeText[s],      // Text
                                         WS_VISIBLE |        // Style
                                              BS_RADIOBUTTON,
                                         52 * cxChar,        // Position
                                         (31 - 3 * s) * cyChar / 2,
                                         22 * cxChar,        // Width
                                         3 * cyChar / 2,     // Height
                                         hwnd,               // Owner
                                         HWND_BOTTOM,        // Placement
                                         s + 16,             // ID
                                         NULL,               // Ctrl Data
                                         NULL) ;             // Pres Params

                WinSendMsg (hwndRadioColor[sCurrentColor],
                            BM_SETCHECK, MPFROMSHORT (1), NULL) ;

                WinSendMsg (hwndRadioType[sCurrentType],
                            BM_SETCHECK, MPFROMSHORT (1), NULL) ;

                aptl[0].x = aptl[3].x = aptl[4].x = 4 * cxChar ;
                aptl[1].x = aptl[2].x = 80 * cxChar ;

                aptl[0].y = aptl[1].y = aptl[4].y = 2 * cyChar ;
                aptl[2].y = aptl[3].y = 20 * cyChar ;

                return 0 ;

           case WM_CONTROL:
                id = SHORT1FROMMP (mp1) ;

                if (id < 16)             // Color IDs
                     {
                     WinSendMsg (hwndRadioColor[sCurrentColor],
                                 BM_SETCHECK, MPFROMSHORT (0), NULL) ;

                     sCurrentColor = id ;

                     WinSendMsg (hwndRadioColor[sCurrentColor],
                                 BM_SETCHECK, MPFROMSHORT (1), NULL) ;
                     }

                else if (id < 24)        // Line Type IDs
                     {
                     WinSendMsg (hwndRadioType[sCurrentType],
                                 BM_SETCHECK, MPFROMSHORT (0), NULL) ;

                     sCurrentType = id - 16 ;

                     WinSendMsg (hwndRadioType[sCurrentType],
                                 BM_SETCHECK, MPFROMSHORT (1), NULL) ;
                     }
                WinInvalidateRect (hwnd, NULL, TRUE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                GpiSetColor (hps, (LONG) sCurrentColor) ;
                GpiSetLineType (hps, sCurrentType + LINETYPE_DOT) ;
                GpiMove (hps, aptl) ;
                GpiPolyLine (hps, 4L, aptl + 1) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 EASYFONT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\EASYFONT.C

 /*----------------------------------------------
    EASYFONT.C -- Routines for Using Image Fonts
   ----------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "easyfont.h"

 static SHORT sFontSize[6]   = { 80, 100, 120, 140, 180, 240 } ;
 static CHAR  *szFacename[4] = { "System Proportional",
                                 "Courier", "Helv", "Tms Rmn" } ;
 static LONG  alMatch[4][6] ;

 BOOL EzfQueryFonts (HPS hps)
      {
      FONTMETRICS *pfm ;
      HDC         hdc ;
      LONG        lHorzRes, lVertRes, lRequestFonts, lNumberFonts ;
      SHORT       sIndex, sFace, sSize ;

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_FONT_RES, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES,   1L, &lVertRes) ;

      for (sFace = 0 ; sFace < 4 ; sFace++)
           {
           lRequestFonts = 0 ;
           lNumberFonts = GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                                         &lRequestFonts, 0L, NULL) ;
           if (lNumberFonts == 0)
                continue ;

           if (lNumberFonts * sizeof (FONTMETRICS) >= 65536L)
                return FALSE ;

           pfm = malloc ((SHORT) lNumberFonts * sizeof (FONTMETRICS)) ;

           if (pfm == NULL)
                return FALSE ;

           GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                          &lNumberFonts, (LONG) sizeof (FONTMETRICS), pfm) ;

           for (sIndex = 0 ; sIndex < (SHORT) lNumberFonts ; sIndex++)
                if (pfm[sIndex].sXDeviceRes == (SHORT) lHorzRes &&
                    pfm[sIndex].sYDeviceRes == (SHORT) lVertRes &&
                   (pfm[sIndex].fsDefn & 1) == 0)
                     {
                     for (sSize = 0 ; sSize < 6 ; sSize++)
                          if (pfm[sIndex].sNominalPointSize == sFontSize[sSize
                               break ;

                     if (sSize != 6)
                          alMatch[sFace][sSize] = pfm[sIndex].lMatch ;
                     }

           free (pfm) ;
           }
      return TRUE ;
      }

 LONG EzfCreateLogFont (HPS hps, LONG lcid, USHORT idFace, USHORT idSize,
                                            USHORT fsSelection)
      {
      static FATTRS fat ;

      if (idFace > 3 || idSize > 5 || alMatch[idFace][idSize] == 0)
           return FALSE ;

      fat.usRecordLength = sizeof fat ;
      fat.fsSelection    = fsSelection ;
      fat.lMatch         = alMatch[idFace][idSize] ;

      strcpy (fat.szFacename, szFacename[idFace]) ;

      return GpiCreateLogFont (hps, NULL, lcid, &fat) ;
      }


 EASYFONT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP08\EASYFONT.C

 /*----------------------------------------------
    EASYFONT.C -- Routines for Using Image Fonts
   ----------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "easyfont.h"

 static SHORT sFontSize[6]   = { 80, 100, 120, 140, 180, 240 } ;
 static CHAR  *szFacename[4] = { "System Proportional",
                                 "Courier", "Helv", "Tms Rmn" } ;
 static LONG  alMatch[4][6] ;

 BOOL EzfQueryFonts (HPS hps)
      {
      FONTMETRICS *pfm ;
      HDC         hdc ;
      LONG        lHorzRes, lVertRes, lRequestFonts, lNumberFonts ;
      SHORT       sIndex, sFace, sSize ;

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_FONT_RES, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES,   1L, &lVertRes) ;

      for (sFace = 0 ; sFace < 4 ; sFace++)
           {
           lRequestFonts = 0 ;
           lNumberFonts = GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                                         &lRequestFonts, 0L, NULL) ;
           if (lNumberFonts == 0)
                continue ;

           if (lNumberFonts * sizeof (FONTMETRICS) >= 65536L)
                return FALSE ;

           pfm = malloc ((SHORT) lNumberFonts * sizeof (FONTMETRICS)) ;

           if (pfm == NULL)
                return FALSE ;

           GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                          &lNumberFonts, (LONG) sizeof (FONTMETRICS), pfm) ;

           for (sIndex = 0 ; sIndex < (SHORT) lNumberFonts ; sIndex++)
                if (pfm[sIndex].sXDeviceRes == (SHORT) lHorzRes &&
                    pfm[sIndex].sYDeviceRes == (SHORT) lVertRes &&
                   (pfm[sIndex].fsDefn & 1) == 0)
                     {
                     for (sSize = 0 ; sSize < 6 ; sSize++)
                          if (pfm[sIndex].sNominalPointSize == sFontSize[sSize
                               break ;

                     if (sSize != 6)
                          alMatch[sFace][sSize] = pfm[sIndex].lMatch ;
                     }

           free (pfm) ;
           }
      return TRUE ;
      }

 LONG EzfCreateLogFont (HPS hps, LONG lcid, USHORT idFace, USHORT idSize,
                                            USHORT fsSelection)
      {
      static FATTRS fat ;

      if (idFace > 3 || idSize > 5 || alMatch[idFace][idSize] == 0)
           return FALSE ;

      fat.usRecordLength = sizeof fat ;
      fat.fsSelection    = fsSelection ;
      fat.lMatch         = alMatch[idFace][idSize] ;

      strcpy (fat.szFacename, szFacename[idFace]) ;

      return GpiCreateLogFont (hps, NULL, lcid, &fat) ;
      }


 EASYFONT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\EASYFONT.C

 /*----------------------------------------------
    EASYFONT.C -- Routines for Using Image Fonts
   ----------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "easyfont.h"

 static SHORT sFontSize[6]   = { 80, 100, 120, 140, 180, 240 } ;
 static CHAR  *szFacename[4] = { "System Proportional",
                                 "Courier", "Helv", "Tms Rmn" } ;
 static LONG  alMatch[4][6] ;

 BOOL EzfQueryFonts (HPS hps)
      {
      FONTMETRICS *pfm ;
      HDC         hdc ;
      LONG        lHorzRes, lVertRes, lRequestFonts, lNumberFonts ;
      SHORT       sIndex, sFace, sSize ;

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_FONT_RES, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES,   1L, &lVertRes) ;

      for (sFace = 0 ; sFace < 4 ; sFace++)
           {
           lRequestFonts = 0 ;
           lNumberFonts = GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                                         &lRequestFonts, 0L, NULL) ;
           if (lNumberFonts == 0)
                continue ;

           if (lNumberFonts * sizeof (FONTMETRICS) >= 65536L)
                return FALSE ;

           pfm = malloc ((SHORT) lNumberFonts * sizeof (FONTMETRICS)) ;

           if (pfm == NULL)
                return FALSE ;

           GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
                          &lNumberFonts, (LONG) sizeof (FONTMETRICS), pfm) ;

           for (sIndex = 0 ; sIndex < (SHORT) lNumberFonts ; sIndex++)
                if (pfm[sIndex].sXDeviceRes == (SHORT) lHorzRes &&
                    pfm[sIndex].sYDeviceRes == (SHORT) lVertRes &&
                   (pfm[sIndex].fsDefn & 1) == 0)
                     {
                     for (sSize = 0 ; sSize < 6 ; sSize++)
                          if (pfm[sIndex].sNominalPointSize == sFontSize[sSize
                               break ;

                     if (sSize != 6)
                          alMatch[sFace][sSize] = pfm[sIndex].lMatch ;
                     }

           free (pfm) ;
           }
      return TRUE ;
      }

 LONG EzfCreateLogFont (HPS hps, LONG lcid, USHORT idFace, USHORT idSize,
                                            USHORT fsSelection)
      {
      static FATTRS fat ;

      if (idFace > 3 || idSize > 5 || alMatch[idFace][idSize] == 0)
           return FALSE ;

      fat.usRecordLength = sizeof fat ;
      fat.fsSelection    = fsSelection ;
      fat.lMatch         = alMatch[idFace][idSize] ;

      strcpy (fat.szFacename, szFacename[idFace]) ;

      return GpiCreateLogFont (hps, NULL, lcid, &fat) ;
      }


 FONTS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\FONTS.C

 /*----------------------------
    FONTS.C -- GPI Image Fonts
   ----------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "easyfont.h"

 #define LCID_MYFONT 1L

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Fonts" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL    | FCF_HORZSCROLL ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR   *szFace[] = { "System", "Courier",
                                  "Helv",   "Tms Rmn" } ;
      static CHAR   *szSize[] = { "8", "10", "12", "14", "18", "24" } ;
      static CHAR   *szSel[]  = { "Normal",     "Italic",  "Underscore",
                                  "Strike-out", "Bold" } ;
      static CHAR   szBuffer[80] ;
      static HWND   hwndVscroll, hwndHscroll ;
      static USHORT idFace[] = { FONTFACE_SYSTEM, FONTFACE_COUR,
                                 FONTFACE_HELV,   FONTFACE_TIMES } ;
      static USHORT idSize[] = { FONTSIZE_8,  FONTSIZE_10, FONTSIZE_12,
                                 FONTSIZE_14, FONTSIZE_18, FONTSIZE_24 } ;
      static USHORT afsSel[] = { 0, FATTR_SEL_ITALIC,    FATTR_SEL_UNDERSCORE,
                                    FATTR_SEL_STRIKEOUT, FATTR_SEL_BOLD } ;
      static SHORT  sVscrollMax = sizeof idFace / sizeof idFace[0] - 1,
                    sHscrollMax = sizeof afsSel / sizeof afsSel[0] - 1,
                    cxClient, cyClient, sHscrollPos, sVscrollPos ;
      FONTMETRICS   fm ;
      HPS           hps;
      HWND          hwndFrame ;
      POINTL        ptl ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                EzfQueryFonts (hps) ;
                WinReleasePS (hps) ;

                hwndFrame   = WinQueryWindow (hwnd, QW_PARENT, FALSE),
                hwndVscroll = WinWindowFromID (hwndFrame, FID_VERTSCROLL) ;
                hwndHscroll = WinWindowFromID (hwndFrame, FID_HORZSCROLL) ;

                WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
                            MPFROM2SHORT (sVscrollPos, 0),
                            MPFROM2SHORT (0, sVscrollMax)) ;

                WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
                            MPFROM2SHORT (sHscrollPos, 0),
                            MPFROM2SHORT (0, sHscrollMax)) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                     case SB_PAGEUP:
                          sVscrollPos = max (0, sVscrollPos - 1) ;
                          break ;

                     case SB_LINEDOWN:
                     case SB_PAGEDOWN:
                          sVscrollPos = min (sVscrollMax, sVscrollPos + 1) ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sVscrollPos = SHORT1FROMMP (mp2) ;
                          break ;

                     default:
                          return 0 ;
                     }
                WinSendMsg (hwndVscroll, SBM_SETPOS,
                            MPFROM2SHORT (sVscrollPos, 0), NULL) ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_HSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINELEFT:
                     case SB_PAGELEFT:
                          sHscrollPos = max (0, sHscrollPos - 1) ;
                          break ;

                     case SB_LINERIGHT:
                     case SB_PAGERIGHT:
                          sHscrollPos = min (sHscrollMax, sHscrollPos + 1) ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sHscrollPos = SHORT1FROMMP (mp2) ;
                          break ;

                     default:
                          return 0 ;
                     }
                WinSendMsg (hwndHscroll, SBM_SETPOS,
                            MPFROM2SHORT (sHscrollPos, 0), NULL) ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_CHAR:
                switch (CHARMSG(&msg)->vkey)
                     {
                     case VK_LEFT:
                     case VK_RIGHT:
                          return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
                     case VK_UP:
                     case VK_DOWN:
                     case VK_PAGEUP:
                     case VK_PAGEDOWN:
                          return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
                     }
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                ptl.x = 0 ;
                ptl.y = cyClient ;

                for (sIndex = 0 ; sIndex < 6 ; sIndex++)
                     if (EzfCreateLogFont (hps, LCID_MYFONT,
                                           idFace[sVscrollPos],
                                           idSize[sIndex],
                                           afsSel[sHscrollPos]))
                          {
                          GpiSetCharSet (hps, LCID_MYFONT) ;
                          GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                          ptl.y -= fm.lMaxBaselineExt ;

                          GpiCharStringAt (hps, &ptl,
                               (LONG) sprintf (szBuffer, "%s, %s point, %s",
                                               szFace[sVscrollPos],
                                               szSize[sIndex],
                                               szSel[sHscrollPos]),
                               szBuffer) ;

                          GpiSetCharSet (hps, LCID_DEFAULT) ;
                          GpiDeleteSetId (hps, LCID_MYFONT) ;
                          }

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 FREEMEM.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP10\FREEMEM.C

 /*----------------------------------
    FREEMEM.C -- Free Memory Display
   ----------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOS
 #include <os2.h>
 #include <string.h>

 #define ID_TIMER 1

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID    SizeTheWindow (HWND) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "FreeMem" ;
      static ULONG flFrameFlags = FCF_TITLEBAR | FCF_SYSMENU  |
                                  FCF_BORDER   | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;
      SizeTheWindow (hwndFrame) ;

      if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
           {
           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinStopTimer (hab, hwndClient, ID_TIMER) ;
           }
      else
           WinMessageBox (HWND_DESKTOP, hwndClient,
                          "Too many clocks or timers",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID SizeTheWindow (HWND hwndFrame)
      {
      static CHAR szText [] = "1,234,567,890 bytes" ;
      HPS         hps ;
      POINTL      aptl[TXTBOX_COUNT] ;
      RECTL       rcl ;

      hps = WinGetPS (hwndFrame) ;
      GpiQueryTextBox (hps, sizeof szText - 1L, szText, TXTBOX_COUNT, aptl) ;
      WinReleasePS (hps) ;

      rcl.yBottom = 0 ;
      rcl.yTop    = 3 * (aptl[TXTBOX_TOPLEFT].y -
                         aptl[TXTBOX_BOTTOMLEFT].y) / 2 ;
      rcl.xLeft   = 0 ;
      rcl.xRight  = (sizeof szText + 1L) * (aptl[TXTBOX_BOTTOMRIGHT].x -
                    aptl[TXTBOX_BOTTOMLEFT].x) / (sizeof szText - 1L) ;

      WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;

      WinSetWindowPos (hwndFrame, NULL, (SHORT) rcl.xLeft, (SHORT) rcl.yBottom
                       (SHORT) (rcl.xRight - rcl.xLeft),
                       (SHORT) (rcl.yTop - rcl.yBottom), SWP_SIZE | SWP_MOVE)
      }

 VOID FormatNumber (CHAR *pchResult, ULONG ulValue)
      {
      BOOL  fDisplay = FALSE ;
      SHORT sDigit ;
      ULONG ulQuotient, ulDivisor = 1000000000L ;

      for (sDigit = 0 ; sDigit < 10 ; sDigit++)
           {
           ulQuotient = ulValue / ulDivisor ;

           if (fDisplay || ulQuotient > 0 || sDigit == 9)
                {
                fDisplay = TRUE ;

                *pchResult++ = (CHAR) ('0' + ulQuotient) ;

                if ((sDigit % 3 == 0) && sDigit != 9)
                     *pchResult++ = ',' ;
                }
           ulValue -= ulQuotient * ulDivisor ;
           ulDivisor /= 10 ;
           }
      *pchResult = '\0' ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static RECTL rcl ;
      static ULONG ulFreeMem, ulPrevMem ;
      CHAR         szBuffer [24] ;
      HPS          hps;

      switch (msg)
           {
           case WM_SIZE:
                WinQueryWindowRect (hwnd, &rcl) ;
                return 0 ;

           case WM_TIMER:
                DosMemAvail (&ulFreeMem) ;

                if (ulFreeMem != ulPrevMem)
                     {
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     ulPrevMem = ulFreeMem ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                FormatNumber (szBuffer, ulFreeMem) ;
                strcat (szBuffer, " bytes") ;

                WinDrawText (hps, -1, szBuffer, &rcl,
                             CLR_NEUTRAL, CLR_BACKGROUND,
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 GRAFMENU.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP13\GRAFMENU.C

 /*------------------------------------
    GRAFMENU.C -- A Menu with Graphics
   ------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "grafmenu.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass[] = "GrafMenu" ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static MENUITEM miBigHelp = { 0,                       // iPosition
                                    MIS_BITMAP | MIS_HELP,   // afStyle
                                    0,                       // afAttribute
                                    IDM_HELP,                // id
                                    NULL,                    // hwndSubMenu
                                    NULL } ;                 // hItem
      FONTMETRICS     fm ;
      HBITMAP         hbm ;
      HPS             hps ;
      HWND            hwndMenu ;

      switch (msg)
           {
           case WM_CREATE:

                     /*----------------------
                        Load bitmap resource
                       ----------------------*/

                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                hbm = GpiLoadBitmap (hps, NULL, IDB_BIGHELP,
                                     64 * fm.lAveCharWidth / 3,
                                     64 * fm.lMaxBaselineExt / 8) ;
                WinReleasePS (hps) ;

                     /*-----------------------
                        Attach bitmap to menu
                       -----------------------*/

                miBigHelp.hItem = (ULONG) hbm ;

                hwndMenu = WinWindowFromID (
                                WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                FID_MENU) ;

                WinSendMsg (hwndMenu, MM_SETITEM,
                            MPFROM2SHORT (0, TRUE), MPFROMP (&miBigHelp)) ;
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_NEW:
                     case IDM_OPEN:
                     case IDM_SAVE:
                     case IDM_SAVEAS:
                     case IDM_ABOUT:
                          WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                          return 0 ;
                     }
                break ;

           case WM_HELP:
                WinMessageBox (HWND_DESKTOP, hwnd,
                               "Help not yet implemented",
                               szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
                return 0 ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 HDRLIB.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\HDRLIB.C

 /*-----------------------------------------------------------
    HDRLIB.C -- "Handy Drawing Routines" Dynamic Link Library
   -----------------------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include "hdrlib.h"

 SHORT APIENTRY HdrPuts (HPS hps, PPOINTL pptl, PCHAR szText)
      {
      SHORT sLength = strlen (szText) ;

      if (pptl == NULL)
           GpiCharString (hps, (LONG) sLength, szText) ;
      else
           GpiCharStringAt (hps, pptl, (LONG) sLength, szText) ;

      return sLength ;
      }

 SHORT cdecl FAR HdrPrintf (HPS hps, PPOINTL pptl, PCHAR szFormat, ...)
      {
      static CHAR chBuffer [1024] ;
      SHORT       sLength ;
      va_list     pArguments ;

      va_start (pArguments, szFormat) ;
      sLength = vsprintf (chBuffer, szFormat, pArguments) ;

      if (pptl == NULL)
           GpiCharString (hps, (LONG) sLength, chBuffer) ;
      else
           GpiCharStringAt (hps, pptl, (LONG) sLength, chBuffer) ;

      va_end (pArguments) ;
      return sLength ;
      }

 LONG APIENTRY HdrEllipse (HPS hps, LONG lOption, PPOINTL pptl)
      {
      POINTL ptlCurrent ;

      GpiQueryCurrentPosition (hps, &ptlCurrent) ;

      return GpiBox (hps, lOption, pptl, labs (pptl->x - ptlCurrent.x),
                                         labs (pptl->y - ptlCurrent.y)) ;
      }


 HDRTEST.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\HDRTEST.C

 /*--------------------------------------------------------------
    HDRTEST.C -- Program to Test HDRLIB.DLL Dynamic Link Library
   --------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "hdrlib.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "HdrTest" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB            hab ;
      HMQ            hmq ;
      HWND           hwndFrame, hwndClient ;
      QMSG           qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT cxClient, cyClient ;
      HPS          hps;
      POINTL       ptl ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                ptl.x = cxClient / 8 ;
                ptl.y = 3 * cyClient / 4 ;
                HdrPrintf (hps, &ptl, "Welcome to the %s",
                           (PCHAR) "OS/2 Presentation Manager") ;

                ptl.x = cxClient / 8 ;
                ptl.y = cyClient / 4 ;
                HdrPuts (hps, &ptl, "This line was displayed by a ") ;
                HdrPuts (hps, NULL, "routine in a dynamic link library.") ;

                ptl.x = 0 ;
                ptl.y = 0 ;
                GpiMove (hps, &ptl) ;

                ptl.x = cxClient - 1 ;
                ptl.y = cyClient - 1 ;
                HdrEllipse (hps, DRO_OUTLINE, &ptl) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 HEAD.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\HEAD.C

 /*------------------------------
    HEAD.C -- Displays File Head
   ------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <malloc.h>
 #include <stdio.h>
 #include <string.h>
 #include "easyfont.h"
 #include "head.h"

 #define LCID_FIXEDFONT   1L
 #define LCID_BOLDFONT    2L

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY AboutDlgProc  (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY OpenDlgProc   (HWND, USHORT, MPARAM, MPARAM) ;
 SHORT            ParseFileName (CHAR *, CHAR *) ;

 CHAR szClientClass [] = "Head" ;
 CHAR szFileName [80] ;
 HAB  hab ;

 int main (int argc, char *argv[])
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

                // Check for filename parameter and copy to szFileName

      if (argc > 1)
           ParseFileName (szFileName, argv [1]) ;

                // Continue normally

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      if (hwndFrame != NULL)
           {
           WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinDestroyWindow (hwndFrame) ;
           }
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR  szErrorMsg [] = "File not found or could not be opened" ;
      static SHORT cxClient, cyClient, cxChar, cyChar, cyDesc ;
      CHAR         *pcReadBuffer ;
      FILE         *fileInput ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sLength ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                EzfQueryFonts (hps) ;

                if (!EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                                            FONTSIZE_10, 0))
                     {
                     WinReleasePS (hps) ;

                     WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                          "Cannot find a fixed-pitch font.  Load the Courier "
                          "fonts from the Control Panel and try again.",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

                     return 1 ;
                     }
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                GpiSetCharSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_OPEN:
                          if (WinDlgBox (HWND_DESKTOP, hwnd, OpenDlgProc,
                                         NULL, IDD_OPEN, NULL))
                               WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;

                     case IDM_ABOUT:
                          WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc,
                                     NULL, IDD_ABOUT, NULL) ;
                          return 0 ;
                     }
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (szFileName [0] != '\0')
                     {
                     EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                            FONTSIZE_10,    0) ;
                     EzfCreateLogFont (hps, LCID_BOLDFONT,  FONTFACE_COUR,
                                            FONTSIZE_10,    FATTR_SEL_BOLD) ;

                     GpiSetCharSet (hps, LCID_BOLDFONT) ;
                     ptl.x = cxChar ;
                     ptl.y = cyClient - cyChar + cyDesc ;
                     GpiCharStringAt (hps, &ptl, (LONG) strlen (szFileName),
                                                 szFileName) ;
                     ptl.y -= cyChar ;

                     if ((fileInput = fopen (szFileName, "r")) != NULL)
                          {
                          GpiSetCharSet (hps, LCID_FIXEDFONT) ;
                          pcReadBuffer = malloc (cxClient / cxChar) ;

                          while ((ptl.y -= cyChar) > 0 &&
                                 fgets (pcReadBuffer, cxClient / cxChar - 2,
                                        fileInput) != NULL)
                               {
                               sLength = strlen (pcReadBuffer) ;

                               if (pcReadBuffer [sLength - 1] == '\n')
                                    sLength-- ;

                               if (sLength > 0)
                                    GpiCharStringAt (hps, &ptl, (LONG) sLength
                                                          pcReadBuffer) ;
                               }
                          free (pcReadBuffer) ;
                          fclose (fileInput) ;
                          }
                     else           // file cannot be opened
                          {
                          ptl.y -= cyChar ;
                          GpiCharStringAt (hps, &ptl,
                                           (LONG) strlen (szErrorMsg),
                                           szErrorMsg) ;
                          }
                     GpiSetCharSet (hps, LCID_DEFAULT) ;
                     GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                     GpiDeleteSetId (hps, LCID_BOLDFONT) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                     case DID_CANCEL:
                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }

 VOID FillDirListBox (HWND hwnd, CHAR *pcCurrentPath)
      {
      static CHAR szDrive [] = "  :" ;
      FILEFINDBUF findbuf ;
      HDIR        hDir = 1 ;
      SHORT       sDrive ;
      USHORT      usDriveNum, usCurPathLen, usSearchCount = 1 ;
      ULONG       ulDriveMap ;

      DosQCurDisk (&usDriveNum, &ulDriveMap) ;
      pcCurrentPath [0] = (CHAR) usDriveNum + '@' ;
      pcCurrentPath [1] = ':' ;
      pcCurrentPath [2] = '\\' ;
      usCurPathLen = 64 ;
      DosQCurDir (0, pcCurrentPath + 3, &usCurPathLen) ;

      WinSetDlgItemText (hwnd, IDD_PATH, pcCurrentPath) ;
      WinSendDlgItemMsg (hwnd, IDD_DIRLIST, LM_DELETEALL, NULL, NULL) ;

      for (sDrive = 0 ; sDrive < 26 ; sDrive++)
           if (ulDriveMap & 1L << sDrive)
                {
                szDrive [1] = (CHAR) sDrive + 'A' ;

                WinSendDlgItemMsg (hwnd, IDD_DIRLIST, LM_INSERTITEM,
                                   MPFROM2SHORT (LIT_END, 0),
                                   MPFROMP (szDrive)) ;
                }

      DosFindFirst ("*.*", &hDir, 0x0017, &findbuf, sizeof findbuf,
                               &usSearchCount, 0L) ;
      while (usSearchCount)
           {
           if (findbuf.attrFile & 0x0010 &&
                     (findbuf.achName [0] != '.' || findbuf.achName [1]))

                WinSendDlgItemMsg (hwnd, IDD_DIRLIST, LM_INSERTITEM,
                                   MPFROM2SHORT (LIT_SORTASCENDING, 0),
                                   MPFROMP (findbuf.achName)) ;

           DosFindNext (hDir, &findbuf, sizeof findbuf, &usSearchCount) ;
           }
      }

 VOID FillFileListBox (HWND hwnd)
      {
      FILEFINDBUF findbuf ;
      HDIR        hDir = 1 ;
      USHORT      usSearchCount = 1 ;

      WinSendDlgItemMsg (hwnd, IDD_FILELIST, LM_DELETEALL, NULL, NULL) ;

      DosFindFirst ("*.*", &hDir, 0x0007, &findbuf, sizeof findbuf,
                               &usSearchCount, 0L) ;
      while (usSearchCount)
           {
           WinSendDlgItemMsg (hwnd, IDD_FILELIST, LM_INSERTITEM,
                              MPFROM2SHORT (LIT_SORTASCENDING, 0),
                              MPFROMP (findbuf.achName)) ;

           DosFindNext (hDir, &findbuf, sizeof findbuf, &usSearchCount) ;
           }
      }

 MRESULT EXPENTRY OpenDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      static CHAR szCurrentPath [80], szBuffer [80] ;
      SHORT       sSelect ;

      switch (msg)
           {
           case WM_INITDLG:
                FillDirListBox (hwnd, szCurrentPath) ;
                FillFileListBox (hwnd) ;

                WinSendDlgItemMsg (hwnd, IDD_FILEEDIT, EM_SETTEXTLIMIT,
                                         MPFROM2SHORT (80, 0), NULL) ;
                return 0 ;

           case WM_CONTROL:
                if (SHORT1FROMMP (mp1) == IDD_DIRLIST ||
                    SHORT1FROMMP (mp1) == IDD_FILELIST)
                     {
                     sSelect = (USHORT) WinSendDlgItemMsg (hwnd,
                                                   SHORT1FROMMP (mp1),
                                                   LM_QUERYSELECTION, 0L, 0L)

                     WinSendDlgItemMsg (hwnd, SHORT1FROMMP (mp1),
                                        LM_QUERYITEMTEXT,
                                        MPFROM2SHORT (sSelect, sizeof szBuffer
                                        MPFROMP (szBuffer)) ;
                     }

                switch (SHORT1FROMMP (mp1))             // Control ID
                     {
                     case IDD_DIRLIST:
                          switch (SHORT2FROMMP (mp1))   // notification code
                               {
                               case LN_ENTER:
                                    if (szBuffer [0] == ' ')
                                         DosSelectDisk (szBuffer [1] - '@') ;
                                    else
                                         DosChDir (szBuffer, 0L) ;

                                    FillDirListBox (hwnd, szCurrentPath) ;
                                    FillFileListBox (hwnd) ;

                                    WinSetDlgItemText (hwnd, IDD_FILEEDIT, "")
                                    return 0 ;
                               }
                          break ;

                     case IDD_FILELIST:
                          switch (SHORT2FROMMP (mp1))   // notification code
                               {
                               case LN_SELECT:
                                    WinSetDlgItemText (hwnd, IDD_FILEEDIT,
                                                       szBuffer) ;
                                    return 0 ;

                               case LN_ENTER:
                                    ParseFileName (szFileName, szBuffer) ;
                                    WinDismissDlg (hwnd, TRUE) ;
                                    return 0 ;
                               }
                          break ;
                     }
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                          WinQueryDlgItemText (hwnd, IDD_FILEEDIT,
                                               sizeof szBuffer, szBuffer) ;

                          switch (ParseFileName (szCurrentPath, szBuffer))
                               {
                               case 0:
                                    WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                                    FillDirListBox (hwnd, szCurrentPath) ;
                                    FillFileListBox (hwnd) ;
                                    return 0 ;

                               case 1:
                                    FillDirListBox (hwnd, szCurrentPath) ;
                                    FillFileListBox (hwnd) ;
                                    WinSetDlgItemText (hwnd, IDD_FILEEDIT, "")
                                    return 0 ;

                               case 2:
                                    strcpy (szFileName, szCurrentPath) ;
                                    WinDismissDlg (hwnd, TRUE) ;
                                    return 0 ;
                               }
                          break ;

                     case DID_CANCEL:
                          WinDismissDlg (hwnd, FALSE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }

 SHORT ParseFileName (CHAR *pcOut, CHAR *pcIn)
      {
           /*----------------------------------------------------------------
              Input:    pcOut -- Pointer to parsed file specification.
                        pcIn  -- Pointer to raw file specification.

              Returns:  0 -- pcIn had invalid drive or directory.
                        1 -- pcIn was empty or had no filename.
                        2 -- pcOut points to drive, full dir, and file name.

              Changes current drive and directory per pcIn string.
             ----------------------------------------------------------------*

      CHAR   *pcLastSlash, *pcFileOnly ;
      ULONG  ulDriveMap ;
      USHORT usDriveNum, usDirLen = 64 ;

      strupr (pcIn) ;

                // If input string is empty, return 1

      if (pcIn [0] == '\0')
           return 1 ;

                // Get drive from input string or current drive

      if (pcIn [1] == ':')
           {
           if (DosSelectDisk (pcIn [0] - '@'))
                return 0 ;

           pcIn += 2 ;
           }
      DosQCurDisk (&usDriveNum, &ulDriveMap) ;

      *pcOut++ = (CHAR) usDriveNum + '@' ;
      *pcOut++ = ':' ;
      *pcOut++ = '\\' ;

                // If rest of string is empty, return 1

      if (pcIn [0] == '\0')
           return 1 ;

                // Search for last backslash.  If none, could be directory.

      if (NULL == (pcLastSlash = strrchr (pcIn, '\\')))
           {
           if (!DosChDir (pcIn, 0L))
                return 1 ;

                     // Otherwise, get current dir & attach input filename

           DosQCurDir (0, pcOut, &usDirLen) ;

           if (strlen (pcIn) > 12)
                return 0 ;

           if (*(pcOut + strlen (pcOut) - 1) != '\\')
                strcat (pcOut++, "\\") ;

           strcat (pcOut, pcIn) ;
           return 2 ;
           }
                // If the only backslash is at beginning, change to root

      if (pcIn == pcLastSlash)
           {
           DosChDir ("\\", 0L) ;

           if (pcIn [1] == '\0')
                return 1 ;

           strcpy (pcOut, pcIn + 1) ;
           return 2 ;
           }
                // Attempt to change directory -- Get current dir if OK

      *pcLastSlash = '\0' ;

      if (DosChDir (pcIn, 0L))
           return 0 ;

      DosQCurDir (0, pcOut, &usDirLen) ;

                // Append input filename, if any

      pcFileOnly = pcLastSlash + 1 ;

      if (*pcFileOnly == '\0')
           return 1 ;

      if (strlen (pcFileOnly) > 12)
           return 0 ;

      if (*(pcOut + strlen (pcOut) - 1) != '\\')
           strcat (pcOut++, "\\") ;

      strcat (pcOut, pcFileOnly) ;
      return 2 ;
      }


 HELLOBIT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\HELLOBIT.C

 /*-------------------------------------
    HELLOBIT.C -- "Hello, world" Bitmap
   -------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "HelloBit" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR      szHello [] = " Hello, world " ;
      static HBITMAP   hbm ;
      static HDC       hdcMemory ;
      static HPS       hpsMemory ;
      static SHORT     cxClient, cyClient, cxString, cyString ;
      BITMAPINFOHEADER bmp ;
      HPS              hps ;
      POINTL           aptl [4], ptl ;
      SHORT            x, y ;
      SIZEL            sizl ;

      switch (msg)
           {
           case WM_CREATE:

                          /*-------------------------------------------------
                             Open memory DC and create PS associated with it
                            -------------------------------------------------*

                hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

                sizl.cx = 0 ;
                sizl.cy = 0 ;
                hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                                         PU_PELS    | GPIF_DEFAULT |
                                         GPIT_MICRO | GPIA_ASSOC) ;

                          /*-------------------------------------
                             Determine dimensions of text string
                            -------------------------------------*/

                GpiQueryTextBox (hpsMemory, sizeof szHello - 1L,
                                 szHello, 4L, aptl) ;

                cxString = (SHORT) (aptl [TXTBOX_TOPRIGHT].x -
                                    aptl [TXTBOX_TOPLEFT].x) ;

                cyString = (SHORT) (aptl [TXTBOX_TOPLEFT].y -
                                    aptl [TXTBOX_BOTTOMLEFT].y) ;

                          /*-------------------------------------------
                             Create bitmap and set it in the memory PS
                            -------------------------------------------*/

                bmp.cbFix     = sizeof bmp ;
                bmp.cx        = cxString ;
                bmp.cy        = cyString ;
                bmp.cPlanes   = 1 ;
                bmp.cBitCount = 1 ;

                hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, 0L, NULL) ;

                GpiSetBitmap (hpsMemory, hbm) ;

                          /*----------------------------------------
                             Write the text string to the memory PS
                            ----------------------------------------*/

                ptl.x = 0 ;
                ptl.y = - aptl [TXTBOX_BOTTOMLEFT].y ;

                GpiSetColor (hpsMemory, CLR_TRUE) ;
                GpiSetBackColor (hpsMemory, CLR_FALSE) ;
                GpiSetBackMix (hpsMemory, BM_OVERPAINT) ;
                GpiCharStringAt (hpsMemory, &ptl, sizeof szHello - 1L,
                                 szHello) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                for (y = 0 ; y <= cyClient / cyString ; y++)
                     for (x = 0 ; x <= cxClient / cxString ; x++)
                          {
                          aptl[0].x = x * cxString ;    // target lower left
                          aptl[0].y = y * cyString ;

                          aptl[1].x = aptl[0].x + cxString ; // upper right
                          aptl[1].y = aptl[0].y + cyString ;

                          aptl[2].x = 0 ;               // source lower left
                          aptl[2].y = 0 ;

                          GpiBitBlt (hps, hpsMemory, 3L, aptl, ROP_SRCCOPY,
                                     BBO_AND) ;
                          }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                GpiDestroyPS (hpsMemory) ;
                DevCloseDC (hdcMemory) ;
                GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 HEXCALC.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\HEXCALC.C

 /*-------------------------------------
    HEXCALC.C -- Hexadecimal Calculator
   -------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include <ctype.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include "hexcalc.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      HAB  hab ;
      HMQ  hmq ;
      HWND hwndFrame ;
      QMSG qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, CLIENTCLASS, ClientWndProc, 0L, 0) ;

      hwndFrame = WinLoadDlg (HWND_DESKTOP, HWND_DESKTOP,
                              NULL, NULL, ID_HEXCALC, NULL) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinLoadPointer (HWND_DESKTOP, NULL, ID_ICON), NULL) ;

      WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwndFrame, FID_CLIENT)) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 void ShowNumber (HWND hwnd, ULONG ulNumber)
      {
      CHAR szBuffer [20] ;

      WinSetWindowText (WinWindowFromID (hwnd, ESCAPE),
                        strupr (ltoa (ulNumber, szBuffer, 16))) ;
      }

 ULONG CalcIt (ULONG ulFirstNum, SHORT sOperation, ULONG ulNum)
      {
      switch (sOperation)
           {
           case '=' : return ulNum ;
           case '+' : return ulFirstNum +  ulNum ;
           case '-' : return ulFirstNum -  ulNum ;
           case '*' : return ulFirstNum *  ulNum ;
           case '&' : return ulFirstNum &  ulNum ;
           case '|' : return ulFirstNum |  ulNum ;
           case '^' : return ulFirstNum ^  ulNum ;
           case '<' : return ulFirstNum << ulNum ;
           case '>' : return ulFirstNum >> ulNum ;
           case '/' : return ulNum ? ulFirstNum / ulNum : ULONG_MAX ;
           case '%' : return ulNum ? ulFirstNum % ulNum : ULONG_MAX ;
           default  : return 0L ;
           }
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fNewNumber = TRUE ;
      static ULONG ulNumber, ulFirstNum ;
      static SHORT sOperation = '=' ;
      HWND         hwndButton ;
      SHORT        idButton ;

      switch (msg)
           {
           case WM_CHAR:
                if (CHARMSG(&msg)->fs & KC_KEYUP)
                     return 0 ;

                if (CHARMSG(&msg)->fs & KC_VIRTUALKEY)
                     switch (CHARMSG(&msg)->vkey)
                          {
                          case VK_LEFT:
                               if (!(CHARMSG(&msg)->fs & KC_CHAR))
                                    {
                                    CHARMSG(&msg)->chr = '\b' ;
                                    CHARMSG(&msg)->fs |= KC_CHAR ;
                                    }
                               break ;

                          case VK_ESC:
                               CHARMSG(&msg)->chr = ESCAPE ;
                               CHARMSG(&msg)->fs |= KC_CHAR ;
                               break ;

                          case VK_NEWLINE:
                          case VK_ENTER:
                               CHARMSG(&msg)->chr = '=' ;
                               CHARMSG(&msg)->fs |= KC_CHAR ;
                               break ;
                          }

                if (CHARMSG(&msg)->fs & KC_CHAR)
                     {
                     CHARMSG(&msg)->chr = toupper (CHARMSG(&msg)->chr) ;

                     if (hwndButton = WinWindowFromID (hwnd,CHARMSG(&msg)->chr
                          WinSendMsg (hwndButton, BM_CLICK, NULL, NULL) ;
                     else
                          WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                     }
                return 1 ;

           case WM_COMMAND:
                idButton = COMMANDMSG(&msg)->cmd ;

                if (idButton == '\b')                        // backspace
                     ShowNumber (hwnd, ulNumber /= 16) ;

                else if (idButton == ESCAPE)                 // escape
                     ShowNumber (hwnd, ulNumber = 0L) ;

                else if (isxdigit (idButton))                // hex digit
                     {
                     if (fNewNumber)
                          {
                          ulFirstNum = ulNumber ;
                          ulNumber = 0L ;
                          }
                     fNewNumber = FALSE ;

                     if (ulNumber <= ULONG_MAX >> 4)
                          ShowNumber (hwnd,
                               ulNumber = 16 * ulNumber + idButton -
                                    (isdigit (idButton) ? '0' : 'A' - 10)) ;
                     else
                          WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                     }
                else                                         // operation
                     {
                     if (!fNewNumber)
                          ShowNumber (hwnd, ulNumber =
                               CalcIt (ulFirstNum, sOperation, ulNumber)) ;
                     fNewNumber = TRUE ;
                     sOperation = idButton ;
                     }
                return 0 ;

           case WM_BUTTON1DOWN:
                WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 HEXCALC2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP15\HEXCALC2.C

 /*-------------------------------------------------------------------
    HEXCALC2.C -- Hexadecimal Calculator with Clipboard Cut and Paste
   -------------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include <ctype.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include "hexcalc.h"

 #define IDM_COPY    256
 #define IDM_PASTE   257

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      HMQ  hmq ;
      HWND hwndFrame ;
      QMSG qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, CLIENTCLASS, ClientWndProc, 0L, 0) ;

      hwndFrame = WinLoadDlg (HWND_DESKTOP, HWND_DESKTOP,
                              NULL, NULL, ID_HEXCALC, NULL) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinLoadPointer (HWND_DESKTOP, NULL, ID_ICON), NULL) ;

      WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwndFrame, FID_CLIENT)) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 HACCEL AddItemsToSysMenu (HWND hwndFrame)
      {
      static CHAR     *szMenuText [3] = { NULL, "~Copy\tCtrl+Ins",
                                                "~Paste\tShift+Ins" } ;
      static MENUITEM mi [3] = {
                               MIT_END, MIS_SEPARATOR, 0, 0,         NULL, NUL
                               MIT_END, MIS_TEXT,      0, IDM_COPY,  NULL, NUL
                               MIT_END, MIS_TEXT,      0, IDM_PASTE, NULL, NUL
                               } ;
      ACCELTABLE      *pacct ;
      HACCEL          haccel ;
      HWND            hwndSysMenu, hwndSysSubMenu ;
      MENUITEM        miSysMenu ;
      SHORT           idSysMenu, sItem ;

                               // Add items to system menu

      hwndSysMenu = WinWindowFromID (hwndFrame, FID_SYSMENU) ;
      idSysMenu = SHORT1FROMMR (WinSendMsg (hwndSysMenu,
                                            MM_ITEMIDFROMPOSITION,
                                            NULL, NULL)) ;

      WinSendMsg (hwndSysMenu, MM_QUERYITEM,
                  MPFROM2SHORT (idSysMenu, FALSE),
                  MPFROMP (&miSysMenu)) ;

      hwndSysSubMenu = miSysMenu.hwndSubMenu ;

      for (sItem = 0 ; sItem < 3 ; sItem++)
           WinSendMsg (hwndSysSubMenu, MM_INSERTITEM,
                       MPFROMP (mi + sItem),
                       MPFROMP (szMenuText [sItem])) ;

                               // Create and set accelerator table

      pacct = malloc (sizeof (ACCELTABLE) + sizeof (ACCEL)) ;

      pacct->cAccel        = 2 ;    // Number of accelerators
      pacct->codepage      = 0 ;    // Not used

      pacct->aaccel[0].fs  = AF_VIRTUALKEY | AF_CONTROL ;
      pacct->aaccel[0].key = VK_INSERT ;
      pacct->aaccel[0].cmd = IDM_COPY ;

      pacct->aaccel[1].fs  = AF_VIRTUALKEY | AF_SHIFT ;
      pacct->aaccel[1].key = VK_INSERT ;
      pacct->aaccel[1].cmd = IDM_PASTE ;

      haccel = WinCreateAccelTable (hab, pacct) ;
      WinSetAccelTable (hab, haccel, hwndFrame) ;

      free (pacct) ;

      return haccel ;
      }

 VOID EnableSysMenuItem (HWND hwnd, USHORT idItem, BOOL fEnable)
      {
      HWND hwndSysMenu ;

      hwndSysMenu = WinWindowFromID (WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                     FID_SYSMENU) ;

      WinSendMsg (hwndSysMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (idItem, TRUE),
                  MPFROM2SHORT (MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)) ;
      }

 void ShowNumber (HWND hwnd, ULONG ulNumber)
      {
      CHAR szBuffer [20] ;

      WinSetWindowText (WinWindowFromID (hwnd, ESCAPE),
                        strupr (ltoa (ulNumber, szBuffer, 16))) ;
      }

 ULONG CalcIt (ULONG ulFirstNum, SHORT sOperation, ULONG ulNum)
      {
      switch (sOperation)
           {
           case '=' : return ulNum ;
           case '+' : return ulFirstNum +  ulNum ;
           case '-' : return ulFirstNum -  ulNum ;
           case '*' : return ulFirstNum *  ulNum ;
           case '&' : return ulFirstNum &  ulNum ;
           case '|' : return ulFirstNum |  ulNum ;
           case '^' : return ulFirstNum ^  ulNum ;
           case '<' : return ulFirstNum << ulNum ;
           case '>' : return ulFirstNum >> ulNum ;
           case '/' : return ulNum ? ulFirstNum / ulNum : ULONG_MAX ;
           case '%' : return ulNum ? ulFirstNum % ulNum : ULONG_MAX ;
           default  : return 0L ;
           }
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL   fNewNumber = TRUE ;
      static HACCEL haccel ;
      static ULONG  ulNumber, ulFirstNum ;
      static SHORT  sOperation = '=' ;
      HWND          hwndButton ;
      PCHAR         pchClipText ;
      QMSG          qmsg ;
      SEL           selClipText ;
      SHORT         s, sLen, idButton ;

      switch (msg)
           {
           case WM_CREATE:
                haccel = AddItemsToSysMenu (
                               WinQueryWindow (hwnd, QW_PARENT, FALSE)) ;
                return 0 ;

           case WM_CHAR:
                if (CHARMSG(&msg)->fs & KC_KEYUP)
                     return 0 ;

                if (CHARMSG(&msg)->fs & KC_VIRTUALKEY)
                     switch (CHARMSG(&msg)->vkey)
                          {
                          case VK_LEFT:
                               if (!(CHARMSG(&msg)->fs & KC_CHAR))
                                    {
                                    CHARMSG(&msg)->chr = '\b' ;
                                    CHARMSG(&msg)->fs |= KC_CHAR ;
                                    }
                               break ;

                          case VK_ESC:
                               CHARMSG(&msg)->chr = ESCAPE ;
                               CHARMSG(&msg)->fs |= KC_CHAR ;
                               break ;

                          case VK_NEWLINE:
                          case VK_ENTER:
                               CHARMSG(&msg)->chr = '=' ;
                               CHARMSG(&msg)->fs |= KC_CHAR ;
                               break ;
                          }

                if (CHARMSG(&msg)->fs & KC_CHAR)
                     {
                     CHARMSG(&msg)->chr = toupper (CHARMSG(&msg)->chr) ;

                     if (hwndButton = WinWindowFromID (hwnd,CHARMSG(&msg)->chr
                          WinSendMsg (hwndButton, BM_CLICK, NULL, NULL) ;
                     else
                          WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                     }
                return 1 ;

           case WM_COMMAND:
                idButton = COMMANDMSG(&msg)->cmd ;

                if (idButton == IDM_COPY)                    // "Copy"
                     {
                     hwndButton = WinWindowFromID (hwnd, ESCAPE) ;
                     sLen = WinQueryWindowTextLength (hwndButton) + 1 ;

                     DosAllocSeg (sLen, &selClipText, SEG_GIVEABLE) ;
                     pchClipText = MAKEP (selClipText, 0) ;
                     WinQueryWindowText (hwndButton, sLen, pchClipText) ;

                     WinOpenClipbrd (hab) ;
                     WinEmptyClipbrd (hab) ;
                     WinSetClipbrdData (hab, (ULONG) selClipText, CF_TEXT,
                                        CFI_SELECTOR) ;
                     WinCloseClipbrd (hab) ;
                     }

                else if (idButton == IDM_PASTE)              // "Paste"
                     {
                     EnableSysMenuItem (hwnd, IDM_COPY,  FALSE) ;
                     EnableSysMenuItem (hwnd, IDM_PASTE, FALSE) ;

                     WinOpenClipbrd (hab) ;

                     selClipText = (SEL) WinQueryClipbrdData (hab, CF_TEXT) ;

                     if (selClipText != 0)
                          {
                          pchClipText = MAKEP (selClipText, 0) ;

                          for (s = 0 ; pchClipText[s] ; s++)
                               {
                               if (pchClipText[s] == '\r')
                                    WinSendMsg (hwnd, WM_CHAR,
                                                MPFROM2SHORT (KC_CHAR, 1),
                                                MPFROM2SHORT ('=', 0)) ;

                               else if (pchClipText[s] != '\n' &&
                                        pchClipText[s] != ' ')
                                    WinSendMsg (hwnd, WM_CHAR,
                                                MPFROM2SHORT (KC_CHAR, 1),
                                                MPFROM2SHORT (pchClipText[s],
                                                              0)) ;

                               while (WinPeekMsg (hab, &qmsg, NULL, 0, 0,
                                                  PM_NOREMOVE))
                                    {
                                    if (qmsg.msg == WM_QUIT)
                                         {
                                         WinCloseClipbrd (hab) ;
                                         return 0 ;
                                         }
                                    else
                                         {
                                         WinGetMsg (hab, &qmsg, NULL, 0, 0) ;
                                         WinDispatchMsg (hab, &qmsg) ;
                                         }
                                    }
                               }
                          }
                     WinCloseClipbrd (hab) ;

                     EnableSysMenuItem (hwnd, IDM_COPY,  TRUE) ;
                     EnableSysMenuItem (hwnd, IDM_PASTE, TRUE) ;
                     }

                else if (idButton == '\b')                   // backspace
                     ShowNumber (hwnd, ulNumber /= 16) ;

                else if (idButton == ESCAPE)                 // escape
                     ShowNumber (hwnd, ulNumber = 0L) ;

                else if (isxdigit (idButton))                // hex digit
                     {
                     if (fNewNumber)
                          {
                          ulFirstNum = ulNumber ;
                          ulNumber = 0L ;
                          }
                     fNewNumber = FALSE ;

                     if (ulNumber <= ULONG_MAX >> 4)
                          ShowNumber (hwnd,
                               ulNumber = 16 * ulNumber + idButton -
                                    (isdigit (idButton) ? '0' : 'A' - 10)) ;
                     else
                          WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                     }
                else                                         // operation
                     {
                     if (!fNewNumber)
                          ShowNumber (hwnd, ulNumber =
                               CalcIt (ulFirstNum, sOperation, ulNumber)) ;
                     fNewNumber = TRUE ;
                     sOperation = idButton ;
                     }
                return 0 ;

           case WM_BUTTON1DOWN:
                WinAlarm (HWND_DESKTOP, WA_ERROR) ;
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;

           case WM_DESTROY:
                WinDestroyAccelTable (haccel) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 IMAGECAT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\IMAGECAT.C

 /*----------------------------------------
    IMAGECAT.C -- Cat drawn using GpiImage
   ----------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "ImageCat" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BYTE  abCat [] = {
                              0x01, 0xF8, 0x1F, 0x80, 0x01, 0x04, 0x20, 0x80,
                              0x00, 0x8F, 0xF1, 0x00, 0x00, 0x48, 0x12, 0x00,
                              0x00, 0x28, 0x14, 0x00, 0x00, 0x1A, 0x58, 0x00,
                              0x00, 0x08, 0x10, 0x00, 0x00, 0xFC, 0x3F, 0x00,
                              0x00, 0x09, 0x90, 0x00, 0x00, 0xFC, 0x3F, 0x00,
                              0x00, 0x08, 0x10, 0x00, 0x00, 0x07, 0xE0, 0x00,
                              0x00, 0x08, 0x10, 0x00, 0x00, 0x08, 0x10, 0xC0,
                              0x00, 0x08, 0x10, 0x20, 0x00, 0x10, 0x08, 0x10,
                              0x00, 0x10, 0x08, 0x08, 0x00, 0x10, 0x08, 0x04,
                              0x00, 0x20, 0x04, 0x04, 0x00, 0x20, 0x04, 0x04,
                              0x00, 0x20, 0x04, 0x04, 0x00, 0x40, 0x02, 0x04,
                              0x00, 0x40, 0x02, 0x04, 0x00, 0x40, 0x02, 0x04,
                              0x00, 0xC0, 0x03, 0x04, 0x00, 0x9C, 0x39, 0x08,
                              0x00, 0xA2, 0x45, 0x08, 0x00, 0xA2, 0x45, 0x10,
                              0x00, 0xA2, 0x45, 0xE0, 0x00, 0xA2, 0x45, 0x00,
                              0x00, 0xA2, 0x45, 0x00, 0x00, 0xFF, 0xFF, 0x00 }
      static SHORT cxClient, cyClient ;
      HPS          hps ;
      POINTL       ptl ;
      SIZEL        sizl ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                ptl.x = cxClient / 2 - 16 ;
                ptl.y = cyClient / 2 + 16 ;
                GpiMove (hps, &ptl) ;

                sizl.cx = 32 ;
                sizl.cy = 32 ;
                GpiImage (hps, 0L, &sizl, (LONG) sizeof abCat, abCat) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }



 KEYLOOK.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP08\KEYLOOK.C

 /*----------------------------------------
    KEYLOOK.C -- Displays WM_CHAR Messages
   ----------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include "easyfont.h"

 #define LCID_FIXEDFONT 1L
 #define MAX_KEYS       100

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass [] = "KeyLook" ;
 HAB  hab ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;
      if (hwndFrame != NULL)
           {
           WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinDestroyWindow (hwndFrame) ;
           }
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR   szHeader [] = "Scan  Rept  IN TG IC CM DK LK PD KU"
                                  " AL CT SH SC VK CH  Virt  Char" ;
      static CHAR   szUndrLn [] = "----  ----  -- -- -- -- -- -- -- --"
                                  " -- -- -- -- -- --  ----  ----" ;
      static CHAR   szFormat [] = "%4X %4dx  %2d %2d %2d %2d %2d %2d %2d %2d"
                                  " %2d %2d %2d %2d %2d %2d  %4X  %4X  %c" ;

      static SHORT  cxChar, cyChar, cyDesc, cxClient, cyClient, sNextKey ;
      static struct {
                    MPARAM mp1 ;
                    MPARAM mp2 ;
                    BOOL   fValid ;
                    }
                    key [MAX_KEYS] ;
      CHAR          szBuffer [80] ;
      FONTMETRICS   fm ;
      HPS           hps ;
      POINTL        ptl ;
      RECTL         rcl, rclInvalid ;
      SHORT         sKey, sIndex, sFlag ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                EzfQueryFonts (hps) ;

                if (!EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                                            FONTSIZE_10, 0))
                     {
                     WinReleasePS (hps) ;

                     WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                          "Cannot find a fixed-pitch font.  Load the Courier "
                          "fonts from the Control Panel and try again.",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

                     return 1 ;
                     }

                GpiSetCharSet (hps, LCID_FIXEDFONT) ;

                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                GpiSetCharSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_CHAR:
                key [sNextKey].mp1 = mp1 ;
                key [sNextKey].mp2 = mp2 ;
                key [sNextKey].fValid = TRUE ;

                sNextKey = (sNextKey + 1) % MAX_KEYS ;

                WinSetRect (hwnd, &rcl,
                            0, 2 * cyChar, cxClient, cyClient - 2 * cyChar) ;

                WinScrollWindow (hwnd, 0, cyChar, &rcl, &rcl, NULL, NULL,
                                                  SW_INVALIDATERGN) ;
                WinUpdateWindow (hwnd) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
                GpiErase (hps) ;
                EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                                       FONTSIZE_10, 0) ;
                GpiSetCharSet (hps, LCID_FIXEDFONT) ;

                ptl.x = cxChar ;
                ptl.y = cyDesc ;
                GpiCharStringAt (hps, &ptl, sizeof szHeader - 1L, szHeader) ;

                ptl.y += cyChar ;
                GpiCharStringAt (hps, &ptl, sizeof szUndrLn - 1L, szUndrLn) ;

                for (sKey = 0 ; sKey < MAX_KEYS ; sKey++)
                     {
                     ptl.y += cyChar ;

                     sIndex = (sNextKey - sKey - 1 + MAX_KEYS) % MAX_KEYS ;

                     if (ptl.y > rclInvalid.yTop ||
                               ptl.y > cyClient - 2 * cyChar ||
                                    !key [sIndex].fValid)
                          break ;

                     mp1 = key [sIndex].mp1 ;
                     mp2 = key [sIndex].mp2 ;

                     sFlag = CHARMSG(&msg)->fs ;

                     GpiCharStringAt (hps, &ptl,
                          (LONG) sprintf (szBuffer, szFormat,
                                    CHARMSG(&msg)->scancode,
                                    CHARMSG(&msg)->cRepeat,
                                    sFlag & KC_INVALIDCHAR ? 1 : 0,
                                    sFlag & KC_TOGGLE      ? 1 : 0,
                                    sFlag & KC_INVALIDCOMP ? 1 : 0,
                                    sFlag & KC_COMPOSITE   ? 1 : 0,
                                    sFlag & KC_DEADKEY     ? 1 : 0,
                                    sFlag & KC_LONEKEY     ? 1 : 0,
                                    sFlag & KC_PREVDOWN    ? 1 : 0,
                                    sFlag & KC_KEYUP       ? 1 : 0,
                                    sFlag & KC_ALT         ? 1 : 0,
                                    sFlag & KC_CTRL        ? 1 : 0,
                                    sFlag & KC_SHIFT       ? 1 : 0,
                                    sFlag & KC_SCANCODE    ? 1 : 0,
                                    sFlag & KC_VIRTUALKEY  ? 1 : 0,
                                    sFlag & KC_CHAR        ? 1 : 0,
                                    CHARMSG(&msg)->vkey,
                                    CHARMSG(&msg)->chr,
                                    sFlag & KC_CHAR ? CHARMSG(&msg)->chr : ' '
                               szBuffer) ;
                     }
                ptl.y = cyClient - cyChar + cyDesc ;
                GpiCharStringAt (hps, &ptl, sizeof szHeader - 1L, szHeader) ;

                ptl.y -= cyChar ;
                GpiCharStringAt (hps, &ptl, sizeof szUndrLn - 1L, szUndrLn) ;

                GpiSetCharSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 LIFE.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP13\LIFE.C

 /*--------------------------------------
    LIFE.C -- John Conway's Game of Life
   --------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "life.h"

 #define ID_TIMER    1

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass [] = "Life" ;
 HAB  hab ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ICON ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID EnableMenuItem (HWND hwndMenu, SHORT idMenuItem, BOOL fEnable)
      {
      WinSendMsg (hwndMenu, MM_SETITEMATTR,
                  MPFROM2SHORT (idMenuItem, TRUE),
                  MPFROM2SHORT (MIA_DISABLED, fEnable ? 0 : MIA_DISABLED)) ;
      }

 VOID ErrorMsg (HWND hwnd, CHAR *szMessage)
      {
      WinMessageBox (HWND_DESKTOP, hwnd, szMessage, szClientClass, 0,
                     MB_OK | MB_ICONEXCLAMATION) ;
      }

 VOID DrawCell (HPS hps, SHORT x, SHORT y, SHORT cxCell, SHORT cyCell,
                BYTE bCell)
      {
      RECTL rcl ;

      rcl.xLeft   = x * cxCell ;
      rcl.yBottom = y * cyCell ;
      rcl.xRight  = rcl.xLeft   + cxCell - 1 ;
      rcl.yTop    = rcl.yBottom + cyCell - 1 ;

      WinFillRect (hps, &rcl, bCell & 1 ? CLR_NEUTRAL : CLR_BACKGROUND) ;
      }

 VOID DoGeneration (HPS hps, PBYTE pbGrid, SHORT xNumCells, SHORT yNumCells,
                    SHORT cxCell, SHORT cyCell)
      {
      SHORT x, y, sSum ;

      for (y = 0 ; y < yNumCells - 1 ; y++)
           for (x = 0 ; x < xNumCells ; x++)
                {
                if (x == 0 || x == xNumCells - 1 || y == 0)
                     *pbGrid |= *pbGrid << 4 ;
                else
                     {
                     sSum = (*(pbGrid             - 1) +    // Left
                             *(pbGrid - xNumCells - 1) +    // Lower Left
                             *(pbGrid - xNumCells    ) +    // Lower
                             *(pbGrid - xNumCells + 1))     // Lower Right
                                              >> 4 ;

                     sSum += *(pbGrid             + 1) +    // Right
                             *(pbGrid + xNumCells + 1) +    // Upper Right
                             *(pbGrid + xNumCells    ) +    // Upper
                             *(pbGrid + xNumCells - 1) ;    // Upper Left

                     sSum = (sSum | *pbGrid) & 0x0F ;

                     *pbGrid <<= 4 ;

                     if (sSum == 3)
                          *pbGrid |= 1 ;

                     if ((*pbGrid & 1) != *pbGrid >> 4)
                          DrawCell (hps, x, y, cxCell, cyCell, *pbGrid) ;
                     }
                pbGrid++ ;
                }
      }

 VOID DisplayGenerationNum (HPS hps, SHORT xGen, SHORT yGen, LONG lGeneration)
      {
      static CHAR szBuffer [24] = "Generation " ;
      POINTL      ptl ;

      ptl.x = xGen ;
      ptl.y = yGen ;

      ltoa (lGeneration, szBuffer + 11, 10) ;

      GpiSavePS (hps) ;

      GpiSetBackMix (hps, BM_OVERPAINT) ;
      GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer), szBuffer) ;

      GpiRestorePS (hps, -1L) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fTimerGoing ;
      static HWND  hwndMenu ;
      static LONG  lGeneration ;
      static SEL   selGrid ;
      static SHORT cxChar, cyChar, cyDesc, cxClient, cyClient, xGenNum, yGenNu
                   cxCell, cyCell, xNumCells, yNumCells, sCellScale = 1 ;
      FONTMETRICS  fm ;
      HPS          hps ;
      PBYTE        pbGrid ;
      POINTL       ptl ;
      SHORT        x, y ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;
                WinReleasePS (hps) ;

                hwndMenu = WinWindowFromID (
                                WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                FID_MENU) ;
                return 0 ;

           case WM_SIZE:
                if (selGrid)
                     {
                     DosFreeSeg (selGrid) ;
                     selGrid = 0 ;
                     }

                if (fTimerGoing)
                     {
                     WinStopTimer (hab, hwnd, ID_TIMER) ;
                     fTimerGoing = FALSE ;
                     }

                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                xGenNum = cxChar ;
                yGenNum = cyClient - cyChar + cyDesc ;

                cxCell = cxChar * 2 / sCellScale ;
                cyCell = cyChar / sCellScale ;

                xNumCells = cxClient / cxCell ;
                yNumCells = (cyClient - cyChar) / cyCell ;

                if (xNumCells <= 0 || yNumCells <= 0)
                     {
                     ErrorMsg (hwnd, "Not enough room for even one cell.") ;
                     }

                else if ((LONG) xNumCells * yNumCells > 65536L)
                     {
                     ErrorMsg (hwnd, "More than 64K cells not supported.") ;
                     }

                else if (DosAllocSeg (xNumCells * yNumCells, &selGrid, 0))
                     {
                     ErrorMsg (hwnd, "Not enough memory for this many cells.")
                     selGrid = 0 ;
                     }

                else
                     {
                     pbGrid = MAKEP (selGrid, 0) ;

                     for (y = 0 ; y < yNumCells ; y++)
                          for (x = 0 ; x < xNumCells ; x++)
                               *pbGrid++ = 0 ;
                     }

                EnableMenuItem (hwndMenu, IDM_SIZE,  TRUE) ;
                EnableMenuItem (hwndMenu, IDM_START, selGrid != 0) ;
                EnableMenuItem (hwndMenu, IDM_STOP,  FALSE) ;
                EnableMenuItem (hwndMenu, IDM_STEP,  selGrid != 0) ;
                EnableMenuItem (hwndMenu, IDM_CLEAR, selGrid != 0) ;

                lGeneration = 0 ;
                return 0 ;

           case WM_BUTTON1DOWN:
                x = MOUSEMSG(&msg)->x / cxCell ;
                y = MOUSEMSG(&msg)->y / cyCell ;

                if (selGrid && !fTimerGoing && x < xNumCells && y < yNumCells)
                     {
                     pbGrid = MAKEP (selGrid, 0) ;

                     hps = WinGetPS (hwnd) ;

                     DrawCell (hps, x, y, cxCell, cyCell,
                               *(pbGrid + y * xNumCells + x) ^= 1) ;

                     WinReleasePS (hps) ;
                     }
                else
                     WinAlarm (HWND_DESKTOP, WA_WARNING) ;
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_LARGE:
                     case IDM_SMALL:
                     case IDM_TINY:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sCellScale, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, 0)) ;

                          sCellScale = COMMANDMSG(&msg)->cmd ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (sCellScale, TRUE),
                                      MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED))

                          WinSendMsg (hwnd, WM_SIZE, NULL,
                                      MPFROM2SHORT (cxClient, cyClient)) ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;

                     case IDM_START:
                          if (!WinStartTimer (hab, hwnd, ID_TIMER, 1))
                               {
                               ErrorMsg (hwnd, "Too many clocks or timers.") ;
                               }
                          else
                               {
                               fTimerGoing = TRUE ;

                               EnableMenuItem (hwndMenu, IDM_SIZE,  FALSE) ;
                               EnableMenuItem (hwndMenu, IDM_START, FALSE) ;
                               EnableMenuItem (hwndMenu, IDM_STOP,  TRUE) ;
                               EnableMenuItem (hwndMenu, IDM_STEP,  FALSE) ;
                               EnableMenuItem (hwndMenu, IDM_CLEAR, FALSE) ;
                               }
                          return 0 ;

                     case IDM_STOP:
                          WinStopTimer (hab, hwnd, ID_TIMER) ;
                          fTimerGoing = FALSE ;

                          EnableMenuItem (hwndMenu, IDM_SIZE,  TRUE) ;
                          EnableMenuItem (hwndMenu, IDM_START, TRUE) ;
                          EnableMenuItem (hwndMenu, IDM_STOP,  FALSE) ;
                          EnableMenuItem (hwndMenu, IDM_STEP,  TRUE) ;
                          EnableMenuItem (hwndMenu, IDM_CLEAR, TRUE) ;
                          return 0 ;

                     case IDM_STEP:
                          WinSendMsg (hwnd, WM_TIMER, NULL, NULL) ;
                          return 0 ;

                     case IDM_CLEAR:
                          lGeneration = 0L ;

                          pbGrid = MAKEP (selGrid, 0) ;

                          for (y = 0 ; y < yNumCells ; y++)
                               for (x = 0 ; x < xNumCells ; x++)
                                    *pbGrid++ = 0 ;

                          WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;
                     }
                break ;

           case WM_TIMER:
                hps = WinGetPS (hwnd) ;

                DisplayGenerationNum (hps, xGenNum, yGenNum, ++lGeneration) ;

                pbGrid = MAKEP (selGrid, 0) ;

                DoGeneration (hps, pbGrid, xNumCells, yNumCells, cxCell, cyCel

                WinReleasePS (hps) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (selGrid)
                     {
                     for (x = 1 ; x <= xNumCells ; x++)
                          {
                          ptl.x = cxCell * x - 1 ;
                          ptl.y = 0 ;
                          GpiMove (hps, &ptl) ;

                          ptl.y = cyCell * yNumCells - 1 ;
                          GpiLine (hps, &ptl) ;
                          }

                     for (y = 1 ; y <= yNumCells ; y++)
                          {
                          ptl.x = 0 ;
                          ptl.y = cyCell * y - 1 ;
                          GpiMove (hps, &ptl) ;

                          ptl.x = cxCell * xNumCells - 1 ;
                          GpiLine (hps, &ptl) ;
                          }

                     pbGrid = MAKEP (selGrid, 0) ;

                     for (y = 0 ; y < yNumCells ; y++)
                          for (x = 0 ; x < xNumCells ; x++)
                               if (*pbGrid++)
                                    DrawCell (hps, x, y, cxCell, cyCell,
                                              *(pbGrid - 1)) ;

                     DisplayGenerationNum (hps, xGenNum, yGenNum, lGeneration)
                     }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (fTimerGoing)
                     WinStopTimer (hab, hwnd, ID_TIMER) ;

                if (selGrid)
                     DosFreeSeg (selGrid) ;

                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 LINETYPE.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\LINETYPE.C

 /*------------------------------
    LINETYPE.C -- GPI Line Types
   ------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <string.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "LineType" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static struct {
                    LONG lLineType ;
                    CHAR *szLineType ;
                    }
                    show [] =
                    {
                    LINETYPE_DEFAULT       , "LINETYPE_DEFAULT"       ,
                    LINETYPE_DOT           , "LINETYPE_DOT"           ,
                    LINETYPE_SHORTDASH     , "LINETYPE_SHORTDASH"     ,
                    LINETYPE_DASHDOT       , "LINETYPE_DASHDOT"       ,
                    LINETYPE_DOUBLEDOT     , "LINETYPE_DOUBLEDOT"     ,
                    LINETYPE_LONGDASH      , "LINETYPE_LONGDASH"      ,
                    LINETYPE_DASHDOUBLEDOT , "LINETYPE_DASHDOUBLEDOT" ,
                    LINETYPE_SOLID         , "LINETYPE_SOLID"         ,
                    LINETYPE_INVISIBLE     , "LINETYPE_INVISIBLE"     ,
                    LINETYPE_ALTERNATE     , "LINETYPE_ALTERNATE"
                    } ;
      static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
                    sNumTypes = sizeof show / sizeof show[0] ;
      FONTMETRICS   fm ;
      HPS           hps ;
      POINTL        ptl ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sIndex = 0 ; sIndex < sNumTypes ; sIndex ++)
                     {
                     GpiSetLineType (hps, show [sIndex].lLineType) ;

                     ptl.x = cxCaps ;
                     ptl.y = cyClient - 2 * (sIndex + 1) * cyChar + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                                      (LONG) strlen (show [sIndex].szLineType)
                                      show [sIndex].szLineType) ;

                     if (cxClient > 25 * cxCaps)
                          {
                          ptl.x = 24 * cxCaps ;
                          ptl.y += cyChar / 2 - cyDesc ;
                          GpiMove (hps, &ptl) ;

                          ptl.x = cxClient - cxCaps ;
                          GpiLine (hps, &ptl) ;
                          }
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 LOADBMP1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP12\LOADBMP1.C

 /*----------------------------------------------------
    LOADBMP1.C -- Loads a Bitmap Resource and Draws it
   ----------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "loadbmp.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "LoadBmp1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT cxClient, cyClient ;
      HBITMAP      hbm ;
      HPS          hps ;
      POINTL       ptl ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO,
                                     (LONG) cxClient, (LONG) cyClient) ;
                if (hbm)
                     {
                     ptl.x = 0 ;
                     ptl.y = 0 ;

                     WinDrawBitmap (hps, hbm, NULL, &ptl,
                                    CLR_NEUTRAL, CLR_BACKGROUND, DBM_NORMAL) ;

                     GpiDeleteBitmap (hbm) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 LOADBMP2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP12\LOADBMP2.C

 /*----------------------------------------------------
    LOADBMP2.C -- Loads a Bitmap Resource and Draws it
   ----------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "loadbmp.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "LoadBmp2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HBITMAP hbm ;
      HPS            hps ;
      RECTL          rcl ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO, 0L, 0L) ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                if (hbm)
                     WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
                                    CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH)

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (hbm)
                     GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 MINMAX1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\MINMAX1.C

 /*-----------------------------------------------
    MINMAX1.C -- Bitblt of Minimize-Maximize Menu
   -----------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "MinMax1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static LONG  cxMinMax, cyMinMax ;
      static SHORT cxClient, cyClient ;
      HPS          hps ;
      POINTL       aptl[3] ;
      LONG         lRow, lCol ;

      switch (msg)
           {
           case WM_CREATE:
                cxMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CXMINMAXBUTTON)
                cyMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CYMINMAXBUTTON)
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                GpiErase (hps) ;

                for (lRow = 0 ; lRow <= cyClient / cyMinMax ; lRow++)
                     for (lCol = 0 ; lCol <= cxClient / cxMinMax ; lCol++)
                          {
                          aptl[0].x = lCol * cxMinMax ;      // target
                          aptl[0].y = lRow * cyMinMax ;      //   lower left

                          aptl[1].x = aptl[0].x + cxMinMax ; // target
                          aptl[1].y = aptl[0].y + cyMinMax ; //   upper right

                          aptl[2].x = cxClient - cxMinMax ;  // source
                          aptl[2].y = cyClient ;             //   lower left

                          GpiBitBlt (hps, hps, 3L, aptl, ROP_SRCCOPY, BBO_AND)
                          }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 MINMAX2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\MINMAX2.C

 /*-----------------------------------------------
    MINMAX2.C -- Bitblt of Minimize-Maximize Menu
   -----------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "MinMax2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static LONG  cxMinMax, cyMinMax ;
      static SHORT cxClient, cyClient ;
      HPS          hps ;
      POINTL       aptl[4] ;

      switch (msg)
           {
           case WM_CREATE:
                cxMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CXMINMAXBUTTON)
                cyMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CYMINMAXBUTTON)
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                aptl[0].x = 0 ;                    // target lower left
                aptl[0].y = 0 ;

                aptl[1].x = cxClient ;             // target upper right
                aptl[1].y = cyClient ;

                aptl[2].x = cxClient - cxMinMax ;  // source lower left
                aptl[2].y = cyClient ;

                aptl[3].x = cxClient ;             // source upper right
                aptl[3].y = cyClient + cyMinMax ;

                GpiBitBlt (hps, hps, 4L, aptl, ROP_SRCCOPY, BBO_AND) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 MINMAX3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP06\MINMAX3.C

 /*---------------------------------------
    MINMAX3.C -- Minimize-Maximize Bitmap
   ---------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "MinMax3" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT cxClient, cyClient ;
      HBITMAP      hbmMin, hbmMax ;
      HPS          hps ;
      POINTL       aptl [2] ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                hbmMin = WinGetSysBitmap (HWND_DESKTOP, SBMP_MINBUTTON) ;
                hbmMax = WinGetSysBitmap (HWND_DESKTOP, SBMP_MAXBUTTON) ;

                aptl[0].x = 0 ;               // Target lower left
                aptl[0].y = 0 ;
                aptl[1].x = cxClient / 2 ;    // Target upper right
                aptl[1].y = cyClient ;

                WinDrawBitmap (hps, hbmMin, NULL, aptl,
                               CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;

                aptl[0].x = cxClient / 2 ;    // Target left
                aptl[1].x = cxClient ;        // Target right

                WinDrawBitmap (hps, hbmMax, NULL, aptl,
                               CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;

                GpiDeleteBitmap (hbmMin) ;
                GpiDeleteBitmap (hbmMax) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 ORGAN.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP08\ORGAN.C

 /*--------------------------------------
    ORGAN.C --  Play Organ from Keyboard
   --------------------------------------*/

 #define INCL_DOS
 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "organ.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID    EXPENTRY Speaker (USHORT usFreq) ;

 SHORT  xOffset, yOffset, cxCaps, cyChar ;
 USHORT usLastScan ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Organ" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      if (DosPortAccess (0, 0, 0x42, 0x61))   // Don't run if port access fail
           return 1 ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      DosPortAccess (0, 1, 0x42, 0x61) ;
      return 0 ;
      }

 VOID DrawKey (HPS hps, USHORT usScanCode, BOOL fInvert)
      {
      RECTL rcl ;

      rcl.xLeft   = 3 * cxCaps * key[usScanCode].xPos / 2 + xOffset ;
      rcl.yBottom = 3 * cyChar * key[usScanCode].yPos / 2 + yOffset ;
      rcl.xRight  = rcl.xLeft   + 3 * cxCaps ;
      rcl.yTop    = rcl.yBottom + 3 * cyChar / 2 ;

      WinDrawText (hps, -1, key[usScanCode].szKey, &rcl,
                   CLR_NEUTRAL, CLR_BACKGROUND,
                   DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
      if (fInvert)
           WinInvertRect (hps, &rcl) ;

      WinDrawBorder (hps, &rcl, 1, 1, CLR_NEUTRAL, CLR_BACKGROUND,
                     DB_STANDARD) ;
      }

 VOID ProcessKey (HPS hps, USHORT usScanCode, USHORT fsFlags)
      {
      static USHORT ausOctFreq [] = { 262, 277, 294, 311, 330, 349,
                                      370, 392, 415, 440, 466, 494 } ;
      USHORT        usOct, usFreq ;

      if (usScanCode >= NUMSCANS)                       // No scan codes over
           return ;
      if ((usOct = key[usScanCode].sOctave) == -1)      // Non-music key
           return ;

      if (fsFlags & KC_KEYUP)                           // For key up
           {
           if (usLastScan == usScanCode)                // If that's the note
                {
                Speaker (0) ;                           // turn off speaker
                DrawKey (hps, usScanCode, FALSE) ;      // and redraw key
                usLastScan = 0 ;
                }
           return ;
           }
      if (fsFlags & KC_PREVDOWN)                        // Ignore typematics
           return ;

      usFreq = ausOctFreq [key[usScanCode].sNote] ;     // Get frequency

      if (fsFlags & KC_SHIFT)
           usOct += fsFlags & KC_ALT ? 2 : 1 ;          // Higher octave
      else if (fsFlags & KC_CTRL)
           usOct -= fsFlags & KC_ALT ? 2 : 1 ;          // Lower octave

      if (usOct > 4)                                    // Shift frequency
           usFreq <<= (usOct - 4) ;                     //   for octave
      else if (usOct < 4)
           usFreq >>= (4 - usOct) ;

      Speaker (usFreq) ;                                // Turn on speaker
      DrawKey (hps, usScanCode, TRUE) ;                 // Draw the inverted k

      if (usLastScan != 0)
           DrawKey (hps, usLastScan, FALSE) ;           // Redraw previous key
      usLastScan = usScanCode ;                         // Save scan code
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      FONTMETRICS fm ;
      HPS         hps ;
      SHORT       cxClient, cyClient ;
      USHORT      usScanCode ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                xOffset = (cxClient - 25 * 3 * cxCaps / 2) / 2 ;
                yOffset = (cyClient - 6 * cyChar) / 2 ;
                return 0 ;

           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_SCANCODE))
                     break ;

                hps = WinGetPS (hwnd) ;
                ProcessKey (hps, CHARMSG(&msg)->scancode, CHARMSG(&msg)->fs) ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (usScanCode = 0 ; usScanCode < NUMSCANS ; usScanCode++)
                     if (key[usScanCode].xPos != -1)
                          DrawKey (hps, usScanCode, usScanCode == usLastScan)

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 PATTDLG.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\PATTDLG.C

 /*--------------------------------------------------
    PATTDLG.C -- Select GPI Patterns from Dialog Box
   --------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "pattdlg.h"

 typedef struct
      {
      SHORT sPattern ;
      SHORT sColor ;
      BOOL  fBorder ;
      }
      PATTERNSDATA ;

 typedef PATTERNSDATA FAR *PPATTERNSDATA ;

 MRESULT EXPENTRY ClientWndProc  (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY AboutDlgProc   (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY PatternDlgProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass[] = "PattDlg" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR         szAppName [] = "PATTDLG" ;
      static CHAR         szKeyName [] = "SETTINGS" ;
      static PATTERNSDATA pdCurrent = { IDD_DENSE1, IDD_BKGRND, TRUE } ;
      static SHORT        cxClient, cyClient ;
      HPS                 hps ;
      POINTL              ptl ;
      SHORT               sDataLength ;

      switch (msg)
           {
           case WM_CREATE:
                sDataLength = sizeof pdCurrent ;

                WinQueryProfileData (hab, szAppName, szKeyName, &pdCurrent,
                                     &sDataLength) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_PATTERNS:
                          if (WinDlgBox (HWND_DESKTOP, hwnd, PatternDlgProc,
                                         NULL, IDD_PATTERNS, &pdCurrent))

                               WinInvalidateRect (hwnd, NULL, FALSE) ;
                          return 0 ;

                     case IDM_ABOUT:
                          WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc,
                                     NULL, IDD_ABOUT, NULL) ;
                          return 0 ;
                     }
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                GpiSetColor (hps, pdCurrent.sColor -
                                  IDD_BKGRND + CLR_BACKGROUND) ;

                GpiSetPattern (hps, pdCurrent.sPattern -
                                    IDD_DENSE1 + PATSYM_DENSE1) ;

                ptl.x = cxClient / 4 ;
                ptl.y = cyClient / 4 ;
                GpiMove (hps, &ptl) ;

                ptl.x *= 3 ;
                ptl.y *= 3 ;
                GpiBox (hps, pdCurrent.fBorder ? DRO_OUTLINEFILL : DRO_FILL,
                             &ptl, 0L, 0L) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (MBID_YES == WinMessageBox (HWND_DESKTOP, hwnd,
                                     "Save current settings?", szAppName, 0,
                                     MB_YESNO | MB_ICONQUESTION))

                     WinWriteProfileData (hab, szAppName, szKeyName, &pdCurren
                                          sizeof pdCurrent) ;
                break ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                     case DID_CANCEL:
                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY PatternDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
      {
      static PATTERNSDATA  pdLocal ;
      static PPATTERNSDATA ppdCurrent ;

      switch (msg)
           {
           case WM_INITDLG:
                ppdCurrent = MPFROMP (mp2) ;
                pdLocal = *ppdCurrent ;

                WinSendDlgItemMsg (hwnd, pdLocal.sPattern, BM_SETCHECK,
                                   MPFROM2SHORT (TRUE, 0), NULL) ;

                WinSendDlgItemMsg (hwnd, pdLocal.sColor, BM_SETCHECK,
                                   MPFROM2SHORT (TRUE, 0), NULL) ;

                WinSendDlgItemMsg (hwnd, IDD_BORDER, BM_SETCHECK,
                                   MPFROM2SHORT (pdLocal.fBorder, 0), NULL) ;

                WinSetFocus (HWND_DESKTOP,
                             WinWindowFromID (hwnd, pdLocal.sPattern)) ;
                return 1 ;

           case WM_CONTROL:
                if (SHORT1FROMMP (mp1) >= IDD_DENSE1 &&
                    SHORT1FROMMP (mp1) <= IDD_HALFTONE)
                     {
                     WinSendDlgItemMsg (hwnd, pdLocal.sPattern, BM_SETCHECK,
                                        MPFROM2SHORT (FALSE, 0), NULL) ;

                     pdLocal.sPattern = SHORT1FROMMP (mp1) ;

                     WinSendDlgItemMsg (hwnd, pdLocal.sPattern, BM_SETCHECK,
                                        MPFROM2SHORT (TRUE, 0), NULL) ;
                     }

                else if (SHORT1FROMMP (mp1) >= IDD_BKGRND &&
                         SHORT1FROMMP (mp1) <= IDD_PALEGRAY)
                     {
                     WinSendDlgItemMsg (hwnd, pdLocal.sColor, BM_SETCHECK,
                                        MPFROM2SHORT (FALSE, 0), NULL) ;

                     pdLocal.sColor = SHORT1FROMMP (mp1) ;

                     WinSendDlgItemMsg (hwnd, pdLocal.sColor, BM_SETCHECK,
                                              MPFROM2SHORT (TRUE, 0), NULL) ;
                     }
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                          pdLocal.fBorder = (BOOL) WinSendDlgItemMsg (hwnd,
                                    IDD_BORDER, BM_QUERYCHECK, NULL, NULL) ;

                          *ppdCurrent = pdLocal ;

                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;

                     case DID_CANCEL:
                          WinDismissDlg (hwnd, FALSE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }


 PATTERNS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\PATTERNS.C

 /*---------------------------------
    PATTERNS.C -- GPI Area Patterns
   ---------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <string.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Patterns" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static struct {
                    LONG lPatternSymbol ;
                    CHAR *szPatternSymbol ;
                    }
                    show [] =
                    {
                    PATSYM_DEFAULT  , "PATSYM_DEFAULT"  ,
                    PATSYM_DENSE1   , "PATSYM_DENSE1"   ,
                    PATSYM_DENSE2   , "PATSYM_DENSE2"   ,
                    PATSYM_DENSE3   , "PATSYM_DENSE3"   ,
                    PATSYM_DENSE4   , "PATSYM_DENSE4"   ,
                    PATSYM_DENSE5   , "PATSYM_DENSE5"   ,
                    PATSYM_DENSE6   , "PATSYM_DENSE6"   ,
                    PATSYM_DENSE7   , "PATSYM_DENSE7"   ,
                    PATSYM_DENSE8   , "PATSYM_DENSE8"   ,
                    PATSYM_VERT     , "PATSYM_VERT"     ,
                    PATSYM_HORIZ    , "PATSYM_HORIZ"    ,
                    PATSYM_DIAG1    , "PATSYM_DIAG1"    ,
                    PATSYM_DIAG2    , "PATSYM_DIAG2"    ,
                    PATSYM_DIAG3    , "PATSYM_DIAG3"    ,
                    PATSYM_DIAG4    , "PATSYM_DIAG4"    ,
                    PATSYM_NOSHADE  , "PATSYM_NOSHADE"  ,
                    PATSYM_SOLID    , "PATSYM_SOLID"    ,
                    PATSYM_HALFTONE , "PATSYM_HALFTONE" ,
                    PATSYM_BLANK    , "PATSYM_BLANK"    ,
                    } ;
      static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
                    sNumTypes = sizeof show / sizeof show[0] ;
      FONTMETRICS   fm ;
      HPS           hps ;
      POINTL        ptl ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sIndex = 0 ; sIndex < sNumTypes ; sIndex ++)
                     {
                     GpiSetPattern (hps, show [sIndex].lPatternSymbol) ;

                     ptl.x = (sIndex < 10 ? 1 : 33) * cxCaps ;
                     ptl.y = cyClient - (sIndex % 10 * 5 + 4) * cyChar / 2
                                                        + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                           (LONG) strlen (show [sIndex].szPatternSymbol),
                                show [sIndex].szPatternSymbol) ;

                     ptl.x = (sIndex < 10 ? 20 : 52) * cxCaps ;
                            ptl.y -= cyDesc + cyChar / 2 ;
                     GpiMove (hps, &ptl) ;

                     ptl.x += 10 * cxCaps ;
                     ptl.y += 2 * cyChar ;
                     GpiBox (hps, DRO_FILL, &ptl, 0L, 0L) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 POEPOEM.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP12\POEPOEM.C

 /*--------------------------------------------------------
    POEPOEM.C -- Demonstrates Programmer-Defined Resources
   --------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_DOS
 #include <os2.h>
 #include <stdlib.h>
 #include "poepoem.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [10] ;
      static CHAR  szTitleBar [40] ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL    | FCF_ICON ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinLoadString (hab, NULL, IDS_CLASS, sizeof szClientClass, szClientClass
      WinLoadString (hab, NULL, IDS_TITLE, sizeof szTitleBar,    szTitleBar) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, szTitleBar
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HWND   hwndScroll ;
      static PCHAR  pResource ;
      static SEL    selResource ;
      static SHORT  cxClient, cyClient, cxChar, cyChar, cyDesc,
                    sScrollPos, sNumLines ;
      FONTMETRICS   fm ;
      HPS           hps ;
      PCHAR         pText ;
      POINTL        ptl ;
      SHORT         sLineLength, sLine ;
      ULONG         ulSegSize ;

      switch (msg)
           {
           case WM_CREATE:

                     /*-----------------------------------------
                        Load the resource, get size and address
                       -----------------------------------------*/

                DosGetResource (NULL, IDT_TEXT, IDT_POEM, &selResource) ;
                DosSizeSeg (selResource, &ulSegSize) ;
                pResource = MAKEP (selResource, 0) ;

                     /*-----------------------------------------------
                        Determine how many text lines are in resource
                       -----------------------------------------------*/

                pText = pResource ;

                while (pText - pResource < (USHORT) ulSegSize)
                     {
                     if (*pText == '\0' || *pText == '\x1A')
                          break ;

                     if (*pText == '\r')
                          sNumLines ++ ;

                     pText++ ;
                     }

                     /*------------------------------------------
                        Initialize scroll bar range and position
                       ------------------------------------------*/

                hwndScroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_VERTSCROLL) ;

                WinSendMsg (hwndScroll, SBM_SETSCROLLBAR,
                                        MPFROM2SHORT (sScrollPos, 0),
                                        MPFROM2SHORT (0, sNumLines - 1)) ;

                     /*----------------------
                        Query character size
                       ----------------------*/

                hps = WinGetPS (hwnd) ;

                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_CHAR:
                return WinSendMsg (hwndScroll, msg, mp1, mp2) ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                          sScrollPos -= 1 ;
                          break ;

                     case SB_LINEDOWN:
                          sScrollPos += 1 ;
                          break ;

                     case SB_PAGEUP:
                          sScrollPos -= cyClient / cyChar ;
                          break ;

                     case SB_PAGEDOWN:
                          sScrollPos += cyClient / cyChar ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sScrollPos = SHORT1FROMMP (mp2) ;
                          break ;
                     }
                sScrollPos = max (0, min (sScrollPos, sNumLines - 1)) ;

                if (sScrollPos != (SHORT) WinSendMsg (hwndScroll,
                                                      SBM_QUERYPOS, 0L, 0L))
                     {
                     WinSendMsg (hwndScroll, SBM_SETPOS,
                                 MPFROM2SHORT (sScrollPos, 0), NULL) ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                pText = pResource ;

                for (sLine = 0 ; sLine < sNumLines ; sLine++)
                     {
                     sLineLength = 0 ;

                     while (pText [sLineLength] != '\r')
                          sLineLength ++ ;

                     ptl.x = cxChar ;
                     ptl.y = cyClient - cyChar * (sLine + 1 - sScrollPos)
                                      + cyDesc ;

                     GpiCharStringAt (hps, &ptl, (LONG) sLineLength, pText) ;

                     pText += sLineLength + 2 ;
                     }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                DosFreeSeg (selResource) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 POORMENU.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP13\POORMENU.C

 /*----------------------------------
    POORMENU.C -- Poor Person's Menu
   ----------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 #define IDM_ABOUT   10
 #define IDM_HELP    11

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szCaption [] = "Poor Person\'s Menu" ;

 int main (void)
      {
      static CHAR  szClientClass[] = "PoorMenu" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR     *szMenuText [3] = { NULL,
                                          "A~bout PoorMenu...",
                                          "~Help..." } ;
      static MENUITEM mi [3] = {
                               MIT_END, MIS_SEPARATOR, 0, 0,         NULL, NUL
                               MIT_END, MIS_TEXT,      0, IDM_ABOUT, NULL, NUL
                               MIT_END, MIS_TEXT,      0, IDM_HELP,  NULL, NUL
                               } ;
      HWND            hwndSysMenu, hwndSysSubMenu ;
      MENUITEM        miSysMenu ;
      SHORT           sItem, idSysMenu ;

      switch (msg)
           {
           case WM_CREATE:
                hwndSysMenu = WinWindowFromID (
                                   WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                   FID_SYSMENU) ;

                idSysMenu = SHORT1FROMMR (WinSendMsg (hwndSysMenu,
                                                      MM_ITEMIDFROMPOSITION,
                                                      NULL, NULL)) ;

                WinSendMsg (hwndSysMenu, MM_QUERYITEM,
                            MPFROM2SHORT (idSysMenu, FALSE),
                            MPFROMP (&miSysMenu)) ;

                hwndSysSubMenu = miSysMenu.hwndSubMenu ;

                for (sItem = 0 ; sItem < 3 ; sItem++)
                     WinSendMsg (hwndSysSubMenu, MM_INSERTITEM,
                                 MPFROMP (mi + sItem),
                                 MPFROMP (szMenuText [sItem])) ;
                return 0 ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_ABOUT:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "(C) Charles Petzold, 1988",
                                    szCaption, 0, MB_OK | MB_ICONASTERISK) ;
                          return 0 ;

                     case IDM_HELP:
                          WinMessageBox (HWND_DESKTOP, hwnd,
                                    "Help not yet implemented",
                                    szCaption, 0, MB_OK | MB_ICONEXCLAMATION)
                          return 0 ;
                     }
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 RESOURCE.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP12\RESOURCE.C

 /*-------------------------------------------------
    RESOURCE.C -- Uses an Icon and Pointer Resource
   -------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include "resource.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Resource" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_ICON ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HPOINTER hIcon, hptr ;
      static SHORT    cxClient, cyClient, cxIcon, cyIcon ;
      HPS             hps ;
      RECTL           rcl ;

      switch (msg)
           {
           case WM_CREATE:
                hIcon = WinLoadPointer (HWND_DESKTOP, NULL, ID_RESOURCE) ;
                hptr  = WinLoadPointer (HWND_DESKTOP, NULL, IDP_CIRCLE) ;

                cxIcon = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CXICON) ;
                cyIcon = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CYICON) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_MOUSEMOVE:
                WinSetPointer (HWND_DESKTOP, hptr) ;
                return 1 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;
                WinFillRect (hps, &rcl, CLR_CYAN) ;

                WinDrawPointer (hps, 0, 0, hIcon, DP_NORMAL) ;
                WinDrawPointer (hps, 0, cyClient - cyIcon, hIcon, DP_NORMAL) ;
                WinDrawPointer (hps, cxClient - cyIcon, 0, hIcon, DP_NORMAL) ;
                WinDrawPointer (hps, cxClient - cxIcon, cyClient - cyIcon,
                                     hIcon, DP_NORMAL) ;

                WinDrawPointer (hps, cxClient / 3, cyClient / 2, hIcon,
                                                        DP_HALFTONED) ;
                WinDrawPointer (hps, 2 * cxClient / 3, cyClient / 2, hIcon,
                                                        DP_INVERTED) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                WinDestroyPointer (hIcon) ;
                WinDestroyPointer (hptr) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 RULER.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\RULER.C

 /*-------------------------
    RULER.C -- Draw a Ruler
   -------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Ruler" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT sTick[16] = { 100, 25, 35, 25, 50, 25, 35, 25,
                                  70, 25, 35, 25, 50, 25, 35, 25 } ;
      static SHORT cxClient, cyClient, cxChar, cyChar, cyDesc ;
      static SIZEL sizl ;
      CHAR         szBuffer [4] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sIndex ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiSetPS (hps, &sizl, PU_LOENGLISH) ;

                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
                cxChar = (SHORT) fm.lAveCharWidth ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                ptl.x = SHORT1FROMMP (mp2) ;
                ptl.y = SHORT2FROMMP (mp2) ;

                hps = WinGetPS (hwnd) ;
                GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
                GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptl) ;
                WinReleasePS (hps) ;

                cxClient = (SHORT) ptl.x ;
                cyClient = (SHORT) ptl.y ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
                GpiErase (hps) ;

                for (sIndex = 0 ; sIndex < 16 * (SHORT) cxClient / 100 ;
                                  sIndex ++)
                     {
                     ptl.x = 100 * sIndex / 16 ;
                     ptl.y = 0 ;
                     GpiMove (hps, &ptl) ;

                     ptl.y = sTick [sIndex % 16] ;
                     GpiLine (hps, &ptl) ;

                     if (sIndex % 16 == 0)
                          {
                          ptl.x -= cxChar / (sIndex > 160 ? 1 : 2) ;
                          ptl.y += cyDesc ;
                          GpiCharStringAt (hps, &ptl,
                               (LONG) sprintf (szBuffer, "%d", sIndex / 16),
                               szBuffer) ;
                          }
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 SHOWBIT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\SHOWBIT.C

 /*--------------------------------------------------------------------
    SHOWBIT.C -- Loads Bitmap Resources from BITLIB.DLL and Draws Them
   --------------------------------------------------------------------*/

 #define INCL_DOS
 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "ShowBit" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " (Space bar or mouse click for next)",
                                      0L, NULL, 0, &hwndClient) ;
      if (hwndFrame != NULL)
           {
           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinDestroyWindow (hwndFrame) ;
           }
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HMODULE hmodBitLib ;
      static USHORT  idBitmap = 1 ;
      HBITMAP        hbm ;
      HPS            hps ;
      RECTL          rcl ;

      switch (msg)
           {
           case WM_CREATE:
                if (DosLoadModule (NULL, 0, "BITLIB", &hmodBitLib))
                     {
                     WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                                    "Cannot load BITLIB.DLL library",
                                    "ShowBit", 0, MB_OK | MB_ICONEXCLAMATION)
                     return 1 ;
                     }
                return 0 ;

           case WM_CHAR:
                if (  CHARMSG(&msg)->fs & KC_KEYUP ||
                    !(CHARMSG(&msg)->fs & KC_VIRTUALKEY) ||
                    !(CHARMSG(&msg)->vkey == VK_SPACE))
                          break ;

                if (++idBitmap == 10)
                     idBitmap = 1 ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_BUTTON1DOWN:
                if (++idBitmap == 10)
                     idBitmap = 1 ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                hbm = GpiLoadBitmap (hps, hmodBitLib, idBitmap, 0L, 0L) ;

                if (hbm != NULL)
                     {
                     WinQueryWindowRect (hwnd, &rcl) ;

                     WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
                                    CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH)

                     GpiDeleteBitmap (hbm) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                DosFreeModule (hmodBitLib) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 SKETCH.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\SKETCH.C

 /*-------------------------------------
    SKETCH.C -- Mouse Sketching Program
   -------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Sketch" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      if (hwndFrame == NULL)
           WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                          "Not enough memory to create the "
                          "bitmap used for storing images.",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
      else
           {
           WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinDestroyWindow (hwndFrame) ;
           }

      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL      fButton1Down, fButton2Down ;
      static HBITMAP   hbm ;
      static HDC       hdcMemory ;
      static HPS       hpsMemory ;
      static POINTL    ptlPointerPos, aptl [3] ;
      BITMAPINFOHEADER bmp ;
      HPS              hpsWindow ;
      LONG             cxFullScrn, cyFullScrn ;
      SIZEL            sizl ;

      switch (msg)
           {
           case WM_CREATE:
                cxFullScrn = WinQuerySysValue (HWND_DESKTOP, SV_CXFULLSCREEN)
                cyFullScrn = WinQuerySysValue (HWND_DESKTOP, SV_CYFULLSCREEN)

                          /*-------------------------
                             Create Memory DC and PS
                            -------------------------*/

                hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;

                sizl.cx = 0 ;
                sizl.cy = 0 ;
                hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
                                         PU_PELS    | GPIF_DEFAULT |
                                         GPIT_MICRO | GPIA_ASSOC) ;

                          /*----------------------------------------------
                             Create monochrome bitmap, return 1 if cannot
                            ----------------------------------------------*/

                bmp.cbFix     = sizeof bmp ;
                bmp.cx        = (SHORT) cxFullScrn ;
                bmp.cy        = (SHORT) cyFullScrn ;
                bmp.cPlanes   = 1 ;
                bmp.cBitCount = 1 ;
                hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, 0L, NULL) ;

                if (hbm == NULL)
                     {
                     GpiDestroyPS (hpsMemory) ;
                     DevCloseDC (hdcMemory) ;
                     return 1 ;
                     }

                          /*--------------------------------------
                             Set bitmap in memory PS and clear it
                            --------------------------------------*/

                GpiSetBitmap (hpsMemory, hbm) ;

                aptl[1].x = cxFullScrn ;
                aptl[1].y = cyFullScrn ;
                GpiBitBlt (hpsMemory, NULL, 2L, aptl, ROP_ZERO, BBO_OR) ;
                return 0 ;

           case WM_BUTTON1DOWN:
                if (!fButton2Down)
                     WinSetCapture (HWND_DESKTOP, hwnd) ;

                ptlPointerPos.x = MOUSEMSG(&msg)->x ;
                ptlPointerPos.y = MOUSEMSG(&msg)->y ;

                fButton1Down = TRUE ;
                break ;                       // do default processing

           case WM_BUTTON1UP:
                if (!fButton2Down)
                     WinSetCapture (HWND_DESKTOP, NULL) ;

                fButton1Down = FALSE ;
                return 0 ;

           case WM_BUTTON2DOWN:
                if (!fButton1Down)
                     WinSetCapture (HWND_DESKTOP, hwnd) ;

                ptlPointerPos.x = MOUSEMSG(&msg)->x ;
                ptlPointerPos.y = MOUSEMSG(&msg)->y ;

                fButton2Down = TRUE ;
                break ;                       // do default processing

           case WM_BUTTON2UP:
                if (!fButton1Down)
                     WinSetCapture (HWND_DESKTOP, NULL) ;

                fButton2Down = FALSE ;
                return 0 ;

           case WM_MOUSEMOVE:
                if (!fButton1Down && !fButton2Down)
                     break ;

                hpsWindow = WinGetPS (hwnd) ;

                GpiSetColor (hpsMemory, fButton1Down ? CLR_TRUE : CLR_FALSE) ;
                GpiSetColor (hpsWindow,
                             fButton1Down ? CLR_NEUTRAL : CLR_BACKGROUND) ;

                GpiMove (hpsMemory, &ptlPointerPos) ;
                GpiMove (hpsWindow, &ptlPointerPos) ;

                ptlPointerPos.x = MOUSEMSG(&msg)->x ;
                ptlPointerPos.y = MOUSEMSG(&msg)->y ;

                GpiLine (hpsMemory, &ptlPointerPos) ;
                GpiLine (hpsWindow, &ptlPointerPos) ;

                WinReleasePS (hpsWindow) ;
                break ;                       // do default processing

           case WM_PAINT:
                hpsWindow = WinBeginPaint (hwnd, NULL, (PRECTL) aptl) ;

                aptl[2] = aptl[0] ;

                GpiBitBlt (hpsWindow, hpsMemory, 3L, aptl, ROP_SRCCOPY,
                           BBO_OR) ;

                WinEndPaint (hpsWindow) ;
                return 0 ;

           case WM_DESTROY:
                GpiDestroyPS (hpsMemory) ;
                DevCloseDC (hdcMemory) ;
                GpiDeleteBitmap (hbm) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 SPEAKER.ASM
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP08\SPEAKER.ASM

 ;-------------------------------------------------
 ; SPEAKER.ASM -- Ring 2 routine for ORGAN program
 ;                (Accesses Intel 8255 Timer chip)
 ;-------------------------------------------------

                PUBLIC    Speaker
                .286
                .MODEL    MEDIUM, PASCAL

                .DATA
 ClockFreq      dd   1193180

                .CODE     SPEAKER_TEXT
 Speaker        PROC FAR  Frequency:WORD
                Cli                                ; Disable interrupts

                Mov  BX, Frequency                 ; Get parameter from stack
                Or   BX, BX                        ; Check if it's zero
                Jz   TurnOff                       ; If so, turn off sound

                Mov  AL, 10110110b                 ; Set flags for programming
                Out  43h, AL

                Mov  AX, WORD PTR [ClockFreq]      ; Calculate timer frequency
                Mov  DX, WORD PTR [ClockFreq + 2]
                Div  BX

                Out  42h, AL                       ; Output low byte
                Jmp  $ + 2                         ; Delay

                Mov  AL, AH                        ; Output high byte
                Out  42h, AL
                Jmp  $ + 2                         ; Delay

                In   AL, 61h                       ; Get 8255 bits
                Jmp  $ + 2                         ; Delay

                Or   AL, 00000011b                 ; Set bits for speaker
                Out  61h, AL                       ; Set 8255 bits
                Jmp  Return

 TurnOff:       In   AL, 61h                       ; Get 8255 bits
                Jmp  $ + 2                         ; Delay
                And  AL, 11111101b                 ; Set bits for no speaker
                Out  61h, AL                       ; Set 8255 bits

 Return:        Sti                                ; Enable interrupts
                Ret
 Speaker        ENDP
                END


 SPIRAL.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\SPIRAL.C

 /*--------------------------------
    SPIRAL.C -- GPI Spiral Drawing
  ---------------------------------*/

 #include <os2.h>
 #include <math.h>

 #define NUMPOINTS 1000
 #define NUMREV    20
 #define PI        3.14159

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Spiral" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT cxClient, cyClient ;
      double       dAngle, dScale ;
      HPS          hps ;
      PPOINTL      pptl ;
      SEL          sel ;
      SHORT        sIndex ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                if (!DosAllocSeg (NUMPOINTS * sizeof (POINTL), &sel, 0))
                     {
                     pptl = MAKEP (sel, 0) ;

                     for (sIndex = 0 ; sIndex < NUMPOINTS ; sIndex ++)
                         {
                         dAngle = sIndex * 2 * PI / (NUMPOINTS / NUMREV) ;
                         dScale = 1 - (double) sIndex / NUMPOINTS ;

                         pptl[sIndex].x = (LONG) (cxClient / 2 *
                                                  (1 + dScale * cos (dAngle)))

                         pptl[sIndex].y = (LONG) (cyClient / 2 *
                                                  (1 + dScale * sin (dAngle)))
                         }
                     GpiMove (hps, pptl) ;
                     GpiPolyLine (hps, NUMPOINTS - 1L, pptl + 1) ;

                     DosFreeSeg (sel) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 SQABOUT.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\SQABOUT.C

 /*---------------------------------------------------------------
    SQABOUT.C -- Demonstration of About Box with Square 3D Button
   ---------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>
 #include "aboutbox.h"

 VOID RegisterSqBtnClass (HAB) ;         // In SQBTN.C

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY AboutDlgProc  (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass[] = "SqAbout" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ICON ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      RegisterSqBtnClass (hab) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_NEW:
                     case IDM_OPEN:
                     case IDM_SAVE:
                     case IDM_SAVEAS:
                          WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                          return 0 ;

                     case IDM_ABOUT:
                          WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc,
                                     NULL, IDD_ABOUT, NULL) ;
                          return 0 ;
                     }
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY AboutDlgProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case DID_OK:
                     case DID_CANCEL:
                          WinDismissDlg (hwnd, TRUE) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefDlgProc (hwnd, msg, mp1, mp2) ;
      }


 SQBTN.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP14\SQBTN.C

 /*----------------------------------------------------------------
    SQBTN.C -- Contains window procedure for square 3D push button
   ----------------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <malloc.h>
 #include <string.h>

 #define LCID_ITALIC 1L

                /*--------------------------------------------------
                   Structure for storing data unique to each window
                  --------------------------------------------------*/
 typedef struct
      {
      PSZ  pszText ;
      BOOL fHaveCapture ;
      BOOL fHaveFocus ;
      BOOL fInsideRect ;
      BOOL fSpaceDown ;
      }
      SQBTN ;

 typedef SQBTN FAR *PSQBTN ;

 MRESULT EXPENTRY SqBtnWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID             DrawButton   (HWND, HPS, PSQBTN) ;

 HAB  hab ;

           /*--------------------------------------------------------
              RegisterSqBtnClass function available to other modules
             --------------------------------------------------------*/

 BOOL RegisterSqBtnClass (HAB habIn)
      {
      hab = habIn ;

      return WinRegisterClass (hab, "SqBtn", SqBtnWndProc,
                               CS_SIZEREDRAW, sizeof (PSQBTN)) ;
      }

           /*-------------------------------------------
              String functions that accept far pointers
             -------------------------------------------*/

 USHORT fstrlen (PCHAR pch)
      {
      USHORT usLen ;
      for (usLen = 0 ; pch[usLen] ; usLen++) ;
      return usLen ;
      }

 PCHAR fstrcpy (PCHAR pchDst, PCHAR pchSrc)
      {
      USHORT usIndex ;
      for (usIndex = 0 ; pchDst[usIndex] = pchSrc[usIndex] ; usIndex++) ;
      return pchDst ;
      }

           /*-------------------------------
              SqBtnWndProc window procedure
             -------------------------------*/

 MRESULT EXPENTRY SqBtnWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      BOOL          fTestInsideRect ;
      HPS           hps ;
      PCREATESTRUCT pcrst ;
      POINTL        ptl ;
      PSQBTN        pSqBtn ;
      PWNDPARAMS    pwprm ;
      RECTL         rcl ;

      pSqBtn = WinQueryWindowPtr (hwnd, 0) ;

      switch (msg)
           {
           case WM_CREATE:
                pSqBtn = _fmalloc (sizeof (SQBTN)) ;

                          // Initialize structure

                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fHaveFocus   = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;
                pSqBtn->fSpaceDown   = FALSE ;

                          // Get window text from creation structure

                pcrst = (PCREATESTRUCT) PVOIDFROMMP (mp2) ;

                pSqBtn->pszText = _fmalloc (1 + fstrlen (pcrst->pszText)) ;
                fstrcpy (pSqBtn->pszText, pcrst->pszText) ;

                WinSetWindowPtr (hwnd, 0, pSqBtn) ;
                return 0 ;

           case WM_SETWINDOWPARAMS:
                pwprm = (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Get window text from window parameter structure

                if (pwprm->fsStatus & WPM_TEXT)
                     {
                     _ffree (pSqBtn->pszText) ;
                     pSqBtn->pszText = _fmalloc (1 + pwprm->cchText) ;
                     fstrcpy (pSqBtn->pszText, pwprm->pszText) ;
                     }
                return 1 ;

           case WM_QUERYWINDOWPARAMS:
                pwprm == (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Set window parameter structure fields

                if (pwprm->fsStatus & WPM_CCHTEXT)
                     pwprm->cchText = fstrlen (pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_TEXT)
                     fstrcpy (pwprm->pszText, pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_CBPRESPARAMS)
                     pwprm->cbPresParams = 0 ;

                if (pwprm->fsStatus & WPM_PRESPARAMS)
                     pwprm->pPresParams = NULL ;

                if (pwprm->fsStatus & WPM_CBCTLDATA)
                     pwprm->cbCtlData = 0 ;

                if (pwprm->fsStatus & WPM_CTLDATA)
                     pwprm->pCtlData = NULL ;

                return 1 ;

           case WM_BUTTON1DOWN:
                WinSetFocus (HWND_DESKTOP, hwnd) ;
                WinSetCapture (HWND_DESKTOP, hwnd) ;
                pSqBtn->fHaveCapture = TRUE ;
                pSqBtn->fInsideRect  = TRUE ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_MOUSEMOVE:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Test if mouse pointer is still in window

                fTestInsideRect = WinPtInRect (hab, &rcl, &ptl) ;

                if (pSqBtn->fInsideRect != fTestInsideRect)
                     {
                     pSqBtn->fInsideRect = fTestInsideRect ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                break ;

           case WM_BUTTON1UP:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinSetCapture (HWND_DESKTOP, NULL) ;
                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Post WM_COMMAND if mouse pointer is in window

                if (WinPtInRect (hab, &rcl, &ptl))
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, TRUE)) ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_ENABLE:
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_SETFOCUS:
                pSqBtn->fHaveFocus = SHORT1FROMMP (mp2) ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY) ||
                      CHARMSG(&msg)->vkey != VK_SPACE    ||
                      CHARMSG(&msg)->fs & KC_PREVDOWN)
                     break ;

                          // Post WM_COMMAND when space bar is released

                if (!(CHARMSG(&msg)->fs & KC_KEYUP))
                     pSqBtn->fSpaceDown = TRUE ;
                else
                     {
                     pSqBtn->fSpaceDown = FALSE ;
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, FALSE)) ;
                     }
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                DrawButton (hwnd, hps, pSqBtn) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                _ffree (pSqBtn->pszText) ;
                _ffree (pSqBtn) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

           /*--------------------------------------------------------
              Draws filled and outlined polygon (used by DrawButton)
             --------------------------------------------------------*/

 VOID Polygon (HPS hps, LONG lPoints, POINTL aptl[], LONG lColor)
      {
                // Draw interior in specified color

      GpiSavePS (hps) ;
      GpiSetColor (hps, lColor) ;

      GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
      GpiMove (hps, aptl) ;
      GpiPolyLine (hps, lPoints - 1, aptl + 1) ;
      GpiEndArea (hps) ;

      GpiRestorePS (hps, -1L) ;

                // Draw boundary in default color

      GpiMove (hps, aptl + lPoints - 1) ;
      GpiPolyLine (hps, lPoints, aptl) ;
      }

           /*---------------------
              Draws Square Button
             ---------------------*/

 VOID DrawButton (HWND hwnd, HPS hps, PSQBTN pSqBtn)
      {
      FATTRS      fat ;
      FONTMETRICS fm ;
      HDC         hdc ;
      LONG        lColor, lHorzRes, lVertRes, cxEdge, cyEdge ;
      POINTL      aptl[10], aptlTextBox[TXTBOX_COUNT], ptlShadow, ptlText ;
      RECTL       rcl ;

                // Find 2 millimeter edge width in pixels

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,   1L, &lVertRes) ;

      cxEdge = lHorzRes / 500 ;
      cyEdge = lVertRes / 500 ;

                // Set up coordinates for drawing the button

      WinQueryWindowRect (hwnd, &rcl) ;

      aptl[0].x = 0 ;                    aptl[0].y = 0 ;
      aptl[1].x = cxEdge ;               aptl[1].y = cyEdge ;
      aptl[2].x = rcl.xRight - cxEdge ;  aptl[2].y = cyEdge ;
      aptl[3].x = rcl.xRight - 1 ;       aptl[3].y = 0 ;
      aptl[4].x = rcl.xRight - 1 ;       aptl[4].y = rcl.yTop - 1 ;
      aptl[5].x = rcl.xRight - cxEdge ;  aptl[5].y = rcl.yTop - cyEdge ;
      aptl[6].x = cxEdge ;               aptl[6].y = rcl.yTop - cyEdge ;
      aptl[7].x = 0 ;                    aptl[7].y = rcl.yTop - 1 ;
      aptl[8].x = 0 ;                    aptl[8].y = 0 ;
      aptl[9].x = cxEdge ;               aptl[9].y = cyEdge ;

                // Paint edges at bottom and right side

      GpiSetColor (hps, CLR_BLACK) ;
      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_PALEGRAY : CLR_DARKGRAY ;
      Polygon (hps, 4L, aptl + 0, lColor) ;
      Polygon (hps, 4L, aptl + 2, lColor) ;

                // Paint edges at top and left side

      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_WHITE ;
      Polygon (hps, 4L, aptl + 4, lColor) ;
      Polygon (hps, 4L, aptl + 6, lColor) ;

                // Paint interior area

      GpiSavePS (hps) ;
      GpiSetColor (hps, (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_PALEGRAY) ;
      GpiMove (hps, aptl + 1) ;
      GpiBox (hps, DRO_FILL, aptl + 5, 0L, 0L) ;
      GpiRestorePS (hps, -1L) ;
      GpiBox (hps, DRO_OUTLINE, aptl + 5, 0L, 0L) ;

                // If button has focus, use italic font

      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

      if (pSqBtn->fHaveFocus)
           {
           fat.usRecordLength  = sizeof fat ;
           fat.fsSelection     = FATTR_SEL_ITALIC ;
           fat.lMatch          = 0 ;
           fat.idRegistry      = fm.idRegistry ;
           fat.usCodePage      = fm.usCodePage ;
           fat.lMaxBaselineExt = fm.lMaxBaselineExt ;
           fat.lAveCharWidth   = fm.lAveCharWidth ;
           fat.fsType          = 0 ;
           fat.fsFontUse       = 0 ;
           strcpy (fat.szFacename, fm.szFacename) ;

           GpiCreateLogFont (hps, NULL, LCID_ITALIC, &fat) ;
           GpiSetCharSet (hps, LCID_ITALIC) ;
           }
                // Calculate text position

      GpiQueryTextBox (hps, (LONG) fstrlen (pSqBtn->pszText), pSqBtn->pszText,
                            TXTBOX_COUNT, aptlTextBox) ;

      ptlText.x = (rcl.xRight - aptlTextBox[TXTBOX_CONCAT].x) / 2 ;
      ptlText.y = (rcl.yTop   - aptlTextBox[TXTBOX_TOPLEFT].y -
                                aptlTextBox[TXTBOX_BOTTOMLEFT].y) / 2 ;

      ptlShadow.x = ptlText.x + fm.lAveCharWidth   / 3 ;
      ptlShadow.y = ptlText.y - fm.lMaxBaselineExt / 8 ;

                // Display text shadow in black, and text in white

      GpiSetColor (hps, CLR_BLACK) ;
      GpiCharStringAt (hps, &ptlShadow, (LONG) fstrlen (pSqBtn->pszText),
                                        pSqBtn->pszText) ;
      GpiSetColor (hps, CLR_WHITE) ;
      GpiCharStringAt (hps, &ptlText, (LONG) fstrlen (pSqBtn->pszText),
                                      pSqBtn->pszText) ;

                // X out button if the window is not enabled

      if (!WinIsWindowEnabled (hwnd))
           {
           GpiMove (hps, aptl + 1) ;
           GpiLine (hps, aptl + 5) ;
           GpiMove (hps, aptl + 2) ;
           GpiLine (hps, aptl + 6) ;
           }
                // Clean up

      if (pSqBtn->fHaveFocus)
           {
           GpiSetCharSet (hps, LCID_DEFAULT) ;
           GpiDeleteSetId (hps, LCID_ITALIC) ;
           }
      }


 SQBTN.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP11\SQBTN.C

 /*----------------------------------------------------------------
    SQBTN.C -- Contains window procedure for square 3D push button
   ----------------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <malloc.h>
 #include <string.h>

 #define LCID_ITALIC 1L

                /*--------------------------------------------------
                   Structure for storing data unique to each window
                  --------------------------------------------------*/
 typedef struct
      {
      PSZ  pszText ;
      BOOL fHaveCapture ;
      BOOL fHaveFocus ;
      BOOL fInsideRect ;
      BOOL fSpaceDown ;
      }
      SQBTN ;

 typedef SQBTN FAR *PSQBTN ;

 MRESULT EXPENTRY SqBtnWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID             DrawButton   (HWND, HPS, PSQBTN) ;

 HAB  hab ;

           /*--------------------------------------------------------
              RegisterSqBtnClass function available to other modules
             --------------------------------------------------------*/

 BOOL RegisterSqBtnClass (HAB habIn)
      {
      hab = habIn ;

      return WinRegisterClass (hab, "SqBtn", SqBtnWndProc,
                               CS_SIZEREDRAW, sizeof (PSQBTN)) ;
      }

           /*-------------------------------------------
              String functions that accept far pointers
             -------------------------------------------*/

 USHORT fstrlen (PCHAR pch)
      {
      USHORT usLen ;
      for (usLen = 0 ; pch[usLen] ; usLen++) ;
      return usLen ;
      }

 PCHAR fstrcpy (PCHAR pchDst, PCHAR pchSrc)
      {
      USHORT usIndex ;
      for (usIndex = 0 ; pchDst[usIndex] = pchSrc[usIndex] ; usIndex++) ;
      return pchDst ;
      }

           /*-------------------------------
              SqBtnWndProc window procedure
             -------------------------------*/

 MRESULT EXPENTRY SqBtnWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      BOOL          fTestInsideRect ;
      HPS           hps ;
      PCREATESTRUCT pcrst ;
      POINTL        ptl ;
      PSQBTN        pSqBtn ;
      PWNDPARAMS    pwprm ;
      RECTL         rcl ;

      pSqBtn = WinQueryWindowPtr (hwnd, 0) ;

      switch (msg)
           {
           case WM_CREATE:
                pSqBtn = _fmalloc (sizeof (SQBTN)) ;

                          // Initialize structure

                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fHaveFocus   = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;
                pSqBtn->fSpaceDown   = FALSE ;

                          // Get window text from creation structure

                pcrst = (PCREATESTRUCT) PVOIDFROMMP (mp2) ;

                pSqBtn->pszText = _fmalloc (1 + fstrlen (pcrst->pszText)) ;
                fstrcpy (pSqBtn->pszText, pcrst->pszText) ;

                WinSetWindowPtr (hwnd, 0, pSqBtn) ;
                return 0 ;

           case WM_SETWINDOWPARAMS:
                pwprm = (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Get window text from window parameter structure

                if (pwprm->fsStatus & WPM_TEXT)
                     {
                     _ffree (pSqBtn->pszText) ;
                     pSqBtn->pszText = _fmalloc (1 + pwprm->cchText) ;
                     fstrcpy (pSqBtn->pszText, pwprm->pszText) ;
                     }
                return 1 ;

           case WM_QUERYWINDOWPARAMS:
                pwprm == (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Set window parameter structure fields

                if (pwprm->fsStatus & WPM_CCHTEXT)
                     pwprm->cchText = fstrlen (pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_TEXT)
                     fstrcpy (pwprm->pszText, pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_CBPRESPARAMS)
                     pwprm->cbPresParams = 0 ;

                if (pwprm->fsStatus & WPM_PRESPARAMS)
                     pwprm->pPresParams = NULL ;

                if (pwprm->fsStatus & WPM_CBCTLDATA)
                     pwprm->cbCtlData = 0 ;

                if (pwprm->fsStatus & WPM_CTLDATA)
                     pwprm->pCtlData = NULL ;

                return 1 ;

           case WM_BUTTON1DOWN:
                WinSetFocus (HWND_DESKTOP, hwnd) ;
                WinSetCapture (HWND_DESKTOP, hwnd) ;
                pSqBtn->fHaveCapture = TRUE ;
                pSqBtn->fInsideRect  = TRUE ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_MOUSEMOVE:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Test if mouse pointer is still in window

                fTestInsideRect = WinPtInRect (hab, &rcl, &ptl) ;

                if (pSqBtn->fInsideRect != fTestInsideRect)
                     {
                     pSqBtn->fInsideRect = fTestInsideRect ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                break ;

           case WM_BUTTON1UP:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinSetCapture (HWND_DESKTOP, NULL) ;
                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Post WM_COMMAND if mouse pointer is in window

                if (WinPtInRect (hab, &rcl, &ptl))
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, TRUE)) ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_ENABLE:
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_SETFOCUS:
                pSqBtn->fHaveFocus = SHORT1FROMMP (mp2) ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY) ||
                      CHARMSG(&msg)->vkey != VK_SPACE    ||
                      CHARMSG(&msg)->fs & KC_PREVDOWN)
                     break ;

                          // Post WM_COMMAND when space bar is released

                if (!(CHARMSG(&msg)->fs & KC_KEYUP))
                     pSqBtn->fSpaceDown = TRUE ;
                else
                     {
                     pSqBtn->fSpaceDown = FALSE ;
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, FALSE)) ;
                     }
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                DrawButton (hwnd, hps, pSqBtn) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                _ffree (pSqBtn->pszText) ;
                _ffree (pSqBtn) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

           /*--------------------------------------------------------
              Draws filled and outlined polygon (used by DrawButton)
             --------------------------------------------------------*/

 VOID Polygon (HPS hps, LONG lPoints, POINTL aptl[], LONG lColor)
      {
                // Draw interior in specified color

      GpiSavePS (hps) ;
      GpiSetColor (hps, lColor) ;

      GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
      GpiMove (hps, aptl) ;
      GpiPolyLine (hps, lPoints - 1, aptl + 1) ;
      GpiEndArea (hps) ;

      GpiRestorePS (hps, -1L) ;

                // Draw boundary in default color

      GpiMove (hps, aptl + lPoints - 1) ;
      GpiPolyLine (hps, lPoints, aptl) ;
      }

           /*---------------------
              Draws Square Button
             ---------------------*/

 VOID DrawButton (HWND hwnd, HPS hps, PSQBTN pSqBtn)
      {
      FATTRS      fat ;
      FONTMETRICS fm ;
      HDC         hdc ;
      LONG        lColor, lHorzRes, lVertRes, cxEdge, cyEdge ;
      POINTL      aptl[10], aptlTextBox[TXTBOX_COUNT], ptlShadow, ptlText ;
      RECTL       rcl ;

                // Find 2 millimeter edge width in pixels

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,   1L, &lVertRes) ;

      cxEdge = lHorzRes / 500 ;
      cyEdge = lVertRes / 500 ;

                // Set up coordinates for drawing the button

      WinQueryWindowRect (hwnd, &rcl) ;

      aptl[0].x = 0 ;                    aptl[0].y = 0 ;
      aptl[1].x = cxEdge ;               aptl[1].y = cyEdge ;
      aptl[2].x = rcl.xRight - cxEdge ;  aptl[2].y = cyEdge ;
      aptl[3].x = rcl.xRight - 1 ;       aptl[3].y = 0 ;
      aptl[4].x = rcl.xRight - 1 ;       aptl[4].y = rcl.yTop - 1 ;
      aptl[5].x = rcl.xRight - cxEdge ;  aptl[5].y = rcl.yTop - cyEdge ;
      aptl[6].x = cxEdge ;               aptl[6].y = rcl.yTop - cyEdge ;
      aptl[7].x = 0 ;                    aptl[7].y = rcl.yTop - 1 ;
      aptl[8].x = 0 ;                    aptl[8].y = 0 ;
      aptl[9].x = cxEdge ;               aptl[9].y = cyEdge ;

                // Paint edges at bottom and right side

      GpiSetColor (hps, CLR_BLACK) ;
      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_PALEGRAY : CLR_DARKGRAY ;
      Polygon (hps, 4L, aptl + 0, lColor) ;
      Polygon (hps, 4L, aptl + 2, lColor) ;

                // Paint edges at top and left side

      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_WHITE ;
      Polygon (hps, 4L, aptl + 4, lColor) ;
      Polygon (hps, 4L, aptl + 6, lColor) ;

                // Paint interior area

      GpiSavePS (hps) ;
      GpiSetColor (hps, (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_PALEGRAY) ;
      GpiMove (hps, aptl + 1) ;
      GpiBox (hps, DRO_FILL, aptl + 5, 0L, 0L) ;
      GpiRestorePS (hps, -1L) ;
      GpiBox (hps, DRO_OUTLINE, aptl + 5, 0L, 0L) ;

                // If button has focus, use italic font

      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

      if (pSqBtn->fHaveFocus)
           {
           fat.usRecordLength  = sizeof fat ;
           fat.fsSelection     = FATTR_SEL_ITALIC ;
           fat.lMatch          = 0 ;
           fat.idRegistry      = fm.idRegistry ;
           fat.usCodePage      = fm.usCodePage ;
           fat.lMaxBaselineExt = fm.lMaxBaselineExt ;
           fat.lAveCharWidth   = fm.lAveCharWidth ;
           fat.fsType          = 0 ;
           fat.fsFontUse       = 0 ;
           strcpy (fat.szFacename, fm.szFacename) ;

           GpiCreateLogFont (hps, NULL, LCID_ITALIC, &fat) ;
           GpiSetCharSet (hps, LCID_ITALIC) ;
           }
                // Calculate text position

      GpiQueryTextBox (hps, (LONG) fstrlen (pSqBtn->pszText), pSqBtn->pszText,
                            TXTBOX_COUNT, aptlTextBox) ;

      ptlText.x = (rcl.xRight - aptlTextBox[TXTBOX_CONCAT].x) / 2 ;
      ptlText.y = (rcl.yTop   - aptlTextBox[TXTBOX_TOPLEFT].y -
                                aptlTextBox[TXTBOX_BOTTOMLEFT].y) / 2 ;

      ptlShadow.x = ptlText.x + fm.lAveCharWidth   / 3 ;
      ptlShadow.y = ptlText.y - fm.lMaxBaselineExt / 8 ;

                // Display text shadow in black, and text in white

      GpiSetColor (hps, CLR_BLACK) ;
      GpiCharStringAt (hps, &ptlShadow, (LONG) fstrlen (pSqBtn->pszText),
                                        pSqBtn->pszText) ;
      GpiSetColor (hps, CLR_WHITE) ;
      GpiCharStringAt (hps, &ptlText, (LONG) fstrlen (pSqBtn->pszText),
                                      pSqBtn->pszText) ;

                // X out button if the window is not enabled

      if (!WinIsWindowEnabled (hwnd))
           {
           GpiMove (hps, aptl + 1) ;
           GpiLine (hps, aptl + 5) ;
           GpiMove (hps, aptl + 2) ;
           GpiLine (hps, aptl + 6) ;
           }
                // Clean up

      if (pSqBtn->fHaveFocus)
           {
           GpiSetCharSet (hps, LCID_DEFAULT) ;
           GpiDeleteSetId (hps, LCID_ITALIC) ;
           }
      }


 SQBTNLIB.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP16\SQBTNLIB.C

 /*---------------------------------------------------------------------
    SQBTNLIB.C -- Dynamic link library version of square 3D push button
   ---------------------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <malloc.h>
 #include <string.h>
 #include "sqbtnlib.h"

 #define LCID_ITALIC 1L

                /*--------------------------------------------------
                   Structure for storing data unique to each window
                  --------------------------------------------------*/
 typedef struct
      {
      PSZ  pszText ;
      BOOL fHaveCapture ;
      BOOL fHaveFocus ;
      BOOL fInsideRect ;
      BOOL fSpaceDown ;
      }
      SQBTN ;

 typedef SQBTN *PSQBTN ;

 MRESULT EXPENTRY SqBtnWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 VOID             DrawButton   (HWND, HPS, PSQBTN) ;

 HAB  hab ;

           /*--------------------------------------------------------
              RegisterSqBtnClass function available to other modules
             --------------------------------------------------------*/

 BOOL APIENTRY RegisterSqBtnClass (HAB habIn)
      {
      hab = habIn ;

      return WinRegisterClass (hab, "SqBtn", SqBtnWndProc,
                               CS_SIZEREDRAW, sizeof (PSQBTN)) ;
      }

           /*-------------------------------
              SqBtnWndProc window procedure
             -------------------------------*/

 MRESULT EXPENTRY SqBtnWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      BOOL          fTestInsideRect ;
      HPS           hps ;
      PCREATESTRUCT pcrst ;
      POINTL        ptl ;
      PSQBTN        pSqBtn ;
      PWNDPARAMS    pwprm ;
      RECTL         rcl ;

      pSqBtn = WinQueryWindowPtr (hwnd, 0) ;

      switch (msg)
           {
           case WM_CREATE:
                pSqBtn = malloc (sizeof (SQBTN)) ;

                          // Initialize structure

                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fHaveFocus   = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;
                pSqBtn->fSpaceDown   = FALSE ;

                          // Get window text from creation structure

                pcrst = (PCREATESTRUCT) PVOIDFROMMP (mp2) ;

                pSqBtn->pszText = malloc (1 + strlen (pcrst->pszText)) ;
                strcpy (pSqBtn->pszText, pcrst->pszText) ;

                WinSetWindowPtr (hwnd, 0, pSqBtn) ;
                return 0 ;

           case WM_SETWINDOWPARAMS:
                pwprm = (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Get window text from window parameter structure

                if (pwprm->fsStatus & WPM_TEXT)
                     {
                     free (pSqBtn->pszText) ;
                     pSqBtn->pszText = malloc (1 + pwprm->cchText) ;
                     strcpy (pSqBtn->pszText, pwprm->pszText) ;
                     }
                return 1 ;

           case WM_QUERYWINDOWPARAMS:
                pwprm == (PWNDPARAMS) PVOIDFROMMP (mp1) ;

                          // Set window parameter structure fields

                if (pwprm->fsStatus & WPM_CCHTEXT)
                     pwprm->cchText = strlen (pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_TEXT)
                     strcpy (pwprm->pszText, pSqBtn->pszText) ;

                if (pwprm->fsStatus & WPM_CBPRESPARAMS)
                     pwprm->cbPresParams = 0 ;

                if (pwprm->fsStatus & WPM_PRESPARAMS)
                     pwprm->pPresParams = NULL ;

                if (pwprm->fsStatus & WPM_CBCTLDATA)
                     pwprm->cbCtlData = 0 ;

                if (pwprm->fsStatus & WPM_CTLDATA)
                     pwprm->pCtlData = NULL ;

                return 1 ;

           case WM_BUTTON1DOWN:
                WinSetFocus (HWND_DESKTOP, hwnd) ;
                WinSetCapture (HWND_DESKTOP, hwnd) ;
                pSqBtn->fHaveCapture = TRUE ;
                pSqBtn->fInsideRect  = TRUE ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_MOUSEMOVE:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Test if mouse pointer is still in window

                fTestInsideRect = WinPtInRect (hab, &rcl, &ptl) ;

                if (pSqBtn->fInsideRect != fTestInsideRect)
                     {
                     pSqBtn->fInsideRect = fTestInsideRect ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                break ;

           case WM_BUTTON1UP:
                if (!pSqBtn->fHaveCapture)
                     break ;

                WinSetCapture (HWND_DESKTOP, NULL) ;
                pSqBtn->fHaveCapture = FALSE ;
                pSqBtn->fInsideRect  = FALSE ;

                WinQueryWindowRect (hwnd, &rcl) ;
                ptl.x = MOUSEMSG(&msg)->x ;
                ptl.y = MOUSEMSG(&msg)->y ;

                          // Post WM_COMMAND if mouse pointer is in window

                if (WinPtInRect (hab, &rcl, &ptl))
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, TRUE)) ;

                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_ENABLE:
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_SETFOCUS:
                pSqBtn->fHaveFocus = SHORT1FROMMP (mp2) ;
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY) ||
                      CHARMSG(&msg)->vkey != VK_SPACE    ||
                      CHARMSG(&msg)->fs & KC_PREVDOWN)
                     break ;

                          // Post WM_COMMAND when space bar is released

                if (!(CHARMSG(&msg)->fs & KC_KEYUP))
                     pSqBtn->fSpaceDown = TRUE ;
                else
                     {
                     pSqBtn->fSpaceDown = FALSE ;
                     WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
                          WM_COMMAND,
                          MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
                          MPFROM2SHORT (CMDSRC_OTHER, FALSE)) ;
                     }
                WinInvalidateRect (hwnd, NULL, FALSE) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                DrawButton (hwnd, hps, pSqBtn) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                free (pSqBtn->pszText) ;
                free (pSqBtn) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

           /*--------------------------------------------------------
              Draws filled and outlined polygon (used by DrawButton)
             --------------------------------------------------------*/

 VOID Polygon (HPS hps, LONG lPoints, POINTL aptl[], LONG lColor)
      {
                // Draw interior in specified color

      GpiSavePS (hps) ;
      GpiSetColor (hps, lColor) ;

      GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
      GpiMove (hps, aptl) ;
      GpiPolyLine (hps, lPoints - 1, aptl + 1) ;
      GpiEndArea (hps) ;

      GpiRestorePS (hps, -1L) ;

                // Draw boundary in default color

      GpiMove (hps, aptl + lPoints - 1) ;
      GpiPolyLine (hps, lPoints, aptl) ;
      }

           /*---------------------
              Draws Square Button
             ---------------------*/

 VOID DrawButton (HWND hwnd, HPS hps, PSQBTN pSqBtn)
      {
      FATTRS      fat ;
      FONTMETRICS fm ;
      HDC         hdc ;
      LONG        lColor, lHorzRes, lVertRes, cxEdge, cyEdge ;
      POINTL      aptl[10], aptlTextBox[TXTBOX_COUNT], ptlShadow, ptlText ;
      RECTL       rcl ;

                // Find 2 millimeter edge width in pixels

      hdc = GpiQueryDevice (hps) ;
      DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &lHorzRes) ;
      DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,   1L, &lVertRes) ;

      cxEdge = lHorzRes / 500 ;
      cyEdge = lVertRes / 500 ;

                // Set up coordinates for drawing the button

      WinQueryWindowRect (hwnd, &rcl) ;

      aptl[0].x = 0 ;                    aptl[0].y = 0 ;
      aptl[1].x = cxEdge ;               aptl[1].y = cyEdge ;
      aptl[2].x = rcl.xRight - cxEdge ;  aptl[2].y = cyEdge ;
      aptl[3].x = rcl.xRight - 1 ;       aptl[3].y = 0 ;
      aptl[4].x = rcl.xRight - 1 ;       aptl[4].y = rcl.yTop - 1 ;
      aptl[5].x = rcl.xRight - cxEdge ;  aptl[5].y = rcl.yTop - cyEdge ;
      aptl[6].x = cxEdge ;               aptl[6].y = rcl.yTop - cyEdge ;
      aptl[7].x = 0 ;                    aptl[7].y = rcl.yTop - 1 ;
      aptl[8].x = 0 ;                    aptl[8].y = 0 ;
      aptl[9].x = cxEdge ;               aptl[9].y = cyEdge ;

                // Paint edges at bottom and right side

      GpiSetColor (hps, CLR_BLACK) ;
      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_PALEGRAY : CLR_DARKGRAY ;
      Polygon (hps, 4L, aptl + 0, lColor) ;
      Polygon (hps, 4L, aptl + 2, lColor) ;

                // Paint edges at top and left side

      lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_WHITE ;
      Polygon (hps, 4L, aptl + 4, lColor) ;
      Polygon (hps, 4L, aptl + 6, lColor) ;

                // Paint interior area

      GpiSavePS (hps) ;
      GpiSetColor (hps, (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
                               CLR_DARKGRAY : CLR_PALEGRAY) ;
      GpiMove (hps, aptl + 1) ;
      GpiBox (hps, DRO_FILL, aptl + 5, 0L, 0L) ;
      GpiRestorePS (hps, -1L) ;
      GpiBox (hps, DRO_OUTLINE, aptl + 5, 0L, 0L) ;

                // If button has focus, use italic font

      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

      if (pSqBtn->fHaveFocus)
           {
           fat.usRecordLength  = sizeof fat ;
           fat.fsSelection     = FATTR_SEL_ITALIC ;
           fat.lMatch          = 0 ;
           fat.idRegistry      = fm.idRegistry ;
           fat.usCodePage      = fm.usCodePage ;
           fat.lMaxBaselineExt = fm.lMaxBaselineExt ;
           fat.lAveCharWidth   = fm.lAveCharWidth ;
           fat.fsType          = 0 ;
           fat.fsFontUse       = 0 ;
           strcpy (fat.szFacename, fm.szFacename) ;

           GpiCreateLogFont (hps, NULL, LCID_ITALIC, &fat) ;
           GpiSetCharSet (hps, LCID_ITALIC) ;
           }
                // Calculate text position

      GpiQueryTextBox (hps, (LONG) strlen (pSqBtn->pszText), pSqBtn->pszText,
                            TXTBOX_COUNT, aptlTextBox) ;

      ptlText.x = (rcl.xRight - aptlTextBox[TXTBOX_CONCAT].x) / 2 ;
      ptlText.y = (rcl.yTop   - aptlTextBox[TXTBOX_TOPLEFT].y -
                                aptlTextBox[TXTBOX_BOTTOMLEFT].y) / 2 ;

      ptlShadow.x = ptlText.x + fm.lAveCharWidth   / 3 ;
      ptlShadow.y = ptlText.y - fm.lMaxBaselineExt / 8 ;

                // Display text shadow in black, and text in white

      GpiSetColor (hps, CLR_BLACK) ;
      GpiCharStringAt (hps, &ptlShadow, (LONG) strlen (pSqBtn->pszText),
                                        pSqBtn->pszText) ;
      GpiSetColor (hps, CLR_WHITE) ;
      GpiCharStringAt (hps, &ptlText, (LONG) strlen (pSqBtn->pszText),
                                      pSqBtn->pszText) ;

                // X out button if the window is not enabled

      if (!WinIsWindowEnabled (hwnd))
           {
           GpiMove (hps, aptl + 1) ;
           GpiLine (hps, aptl + 5) ;
           GpiMove (hps, aptl + 2) ;
           GpiLine (hps, aptl + 6) ;
           }
                // Clean up

      if (pSqBtn->fHaveFocus)
           {
           GpiSetCharSet (hps, LCID_DEFAULT) ;
           GpiDeleteSetId (hps, LCID_ITALIC) ;
           }
      }


 STAR5.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\STAR5.C

 /*---------------------------------
    STAR5.C -- Draws 5-Pointed Star
  ----------------------------------*/

 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Star5" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static POINTL aptlStar[5] = {-59,-81, 0,100, 59,-81, -95,31, 95,31 } ;
      static SHORT  cxClient, cyClient ;
      HPS           hps ;
      POINTL        aptl[5] ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sIndex = 0 ; sIndex < 5 ; sIndex++)
                     {
                     aptl[sIndex].x = cxClient / 2 + cxClient *
                                                  aptlStar[sIndex].x / 200 ;
                     aptl[sIndex].y = cyClient / 2 + cyClient *
                                                  aptlStar[sIndex].y / 200 ;
                     }
                GpiMove (hps, aptl + 4) ;
                GpiPolyLine (hps, 5L, aptl) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 STARFILL.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP05\STARFILL.C

 /*-------------------------------------------
    STARFILL.C -- Alternate and Winding Modes
  --------------------------------------------*/

 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "StarFill" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass,
                                      " - Alternate and Winding",
                                      0L, NULL, 0, &hwndClient) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static POINTL aptlStar[5] = {-59,-81, 0,100, 59,-81, -95,31, 95,31 } ;
      static SHORT  cxClient, cyClient ;
      HPS           hps ;
      POINTL        aptl[5] ;
      SHORT         sIndex ;

      switch (msg)
           {
           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;
                GpiSetPattern (hps, PATSYM_HALFTONE) ;

                          /*---------------------
                             Alternate Fill Mode
                            ---------------------*/

                for (sIndex = 0 ; sIndex < 5 ; sIndex++)
                     {
                     aptl[sIndex].x = cxClient / 4 + cxClient *
                                           aptlStar[sIndex].x / 400 ;
                     aptl[sIndex].y = cyClient / 2 + cyClient *
                                           aptlStar[sIndex].y / 200 ;
                     }

                GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
                GpiMove (hps, aptl) ;
                GpiPolyLine (hps, 4L, aptl + 1) ;
                GpiEndArea (hps) ;

                          /*-------------------
                             Winding Fill Mode
                            -------------------*/

                for (sIndex = 0 ; sIndex < 5 ; sIndex++)
                     aptl[sIndex].x += cxClient / 2 ;

                GpiBeginArea (hps, BA_NOBOUNDARY | BA_WINDING) ;
                GpiMove (hps, aptl) ;
                GpiPolyLine (hps, 4L, aptl + 1) ;
                GpiEndArea (hps) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 SYSVALS.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP04\SYSVALS.C

 /*--------------------------------------------
    SYSVALS.C -- System Values Display Program
   --------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "sysvals.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "SysVals" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL    | FCF_HORZSCROLL ;
      HAB            hab ;
      HMQ            hmq ;
      HWND           hwndFrame, hwndClient ;
      QMSG           qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText)
      {
      POINTL aptlTextBox[TXTBOX_COUNT] ;

      GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;

      pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;

      return GpiCharStringAt (hps, pptl, lLength, pchText) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HWND  hwndHscroll, hwndVscroll ;
      static SHORT sHscrollMax, sVscrollMax, sHscrollPos, sVscrollPos,
                   cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient,
                   cxTextTotal ;
      CHAR         szBuffer [10] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sLine, sPaintBeg, sPaintEnd, sHscrollInc, sVscrollInc ;
      RECTL        rclInvalid ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;

                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                cxChar = (SHORT) fm.lAveCharWidth ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;

                cxTextTotal = 28 * cxCaps + 38 * cxChar ;

                hwndHscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_HORZSCROLL) ;

                hwndVscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_VERTSCROLL) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                sHscrollMax = max (0, cxTextTotal - cxClient) ;
                sHscrollPos = min (sHscrollPos, sHscrollMax) ;

                WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sHscrollPos, 0),
                                         MPFROM2SHORT (0, sHscrollMax)) ;

                WinEnableWindow (hwndHscroll, sHscrollMax ? TRUE : FALSE) ;

                sVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
                sVscrollPos = min (sVscrollPos, sVscrollMax) ;

                WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sVscrollPos, 0),
                                         MPFROM2SHORT (0, sVscrollMax)) ;

                WinEnableWindow (hwndVscroll, sVscrollMax ? TRUE : FALSE) ;
                return 0 ;

           case WM_HSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINELEFT:
                          sHscrollInc = -cxCaps ;
                          break ;

                     case SB_LINERIGHT:
                          sHscrollInc = cxCaps ;
                          break ;

                     case SB_PAGELEFT:
                          sHscrollInc = -8 * cxCaps ;
                          break ;

                     case SB_PAGERIGHT:
                          sHscrollInc = 8 * cxCaps ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sHscrollInc = SHORT1FROMMP (mp2) - sHscrollPos;
                          break ;

                     default:
                          sHscrollInc = 0 ;
                          break ;
                     }

                sHscrollInc = max (-sHscrollPos,
                              min (sHscrollInc, sHscrollMax - sHscrollPos)) ;

                if (sHscrollInc != 0)
                     {
                     sHscrollPos += sHscrollInc ;
                     WinScrollWindow (hwnd, -sHscrollInc, 0,
                                    NULL, NULL, NULL, NULL, SW_INVALIDATERGN)

                     WinSendMsg (hwndHscroll, SBM_SETPOS,
                                 MPFROMSHORT (sHscrollPos), NULL) ;
                     }
                return 0 ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                          sVscrollInc = -1 ;
                          break ;

                     case SB_LINEDOWN:
                          sVscrollInc = 1 ;
                          break ;

                     case SB_PAGEUP:
                          sVscrollInc = min (-1, -cyClient / cyChar) ;
                          break ;

                     case SB_PAGEDOWN:
                          sVscrollInc = max (1, cyClient / cyChar) ;
                          break ;

                     case SB_SLIDERTRACK:
                          sVscrollInc = SHORT1FROMMP (mp2) - sVscrollPos;
                          break ;

                     default:
                          sVscrollInc = 0 ;
                          break ;
                     }

                sVscrollInc = max (-sVscrollPos,
                              min (sVscrollInc, sVscrollMax - sVscrollPos)) ;

                if (sVscrollInc != 0) ;
                     {
                     sVscrollPos += sVscrollInc ;
                     WinScrollWindow (hwnd, 0, cyChar * sVscrollInc,
                                    NULL, NULL, NULL, NULL, SW_INVALIDATERGN)

                     WinSendMsg (hwndVscroll, SBM_SETPOS,
                                 MPFROMSHORT (sVscrollPos), NULL) ;
                     WinUpdateWindow (hwnd) ;
                     }
                return 0 ;

           case WM_CHAR:
                switch (CHARMSG(&msg)->vkey)
                     {
                     case VK_LEFT:
                     case VK_RIGHT:
                          return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
                     case VK_UP:
                     case VK_DOWN:
                     case VK_PAGEUP:
                     case VK_PAGEDOWN:
                          return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
                     }
                break ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
                GpiErase (hps) ;

                sPaintBeg = max (0, sVscrollPos +
                               (cyClient - (SHORT) rclInvalid.yTop) / cyChar)
                sPaintEnd = min (NUMLINES, sVscrollPos +
                               (cyClient - (SHORT) rclInvalid.yBottom)
                                    / cyChar + 1) ;

                for (sLine = sPaintBeg ; sLine < sPaintEnd ; sLine++)
                     {
                     ptl.x = cxCaps - sHscrollPos ;
                     ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
                                      + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szIdentifier),
                               sysvals[sLine].szIdentifier) ;

                     ptl.x += 20 * cxCaps ;
                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szDescription),
                               sysvals[sLine].szDescription) ;

                     ltoa (WinQuerySysValue (HWND_DESKTOP,
                                sysvals[sLine].sIndex), szBuffer, 10) ;

                     ptl.x += 38 * cxChar + 6 * cxCaps ;
                     RtJustCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
                                         szBuffer) ;
                     }

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }



 SYSVALS1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP04\SYSVALS1.C

 /*---------------------------------------------------
    SYSVALS1.C -- System Values Display Program No. 1
   ---------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "sysvals.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "SysVals1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static SHORT cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient ;
      CHAR         szBuffer [10] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sLine ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                cxChar = (SHORT) fm.lAveCharWidth ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sLine = 0 ; sLine < NUMLINES ; sLine++)
                     {
                     ptl.x = cxCaps ;
                     ptl.y = cyClient - cyChar * (sLine + 1) + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szIdentifier),
                               sysvals[sLine].szIdentifier) ;

                     ptl.x += 20 * cxCaps ;
                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szDescription),
                               sysvals[sLine].szDescription) ;

                     ltoa (WinQuerySysValue (HWND_DESKTOP,
                                sysvals[sLine].sIndex), szBuffer, 10) ;

                     ptl.x += 38 * cxChar ;
                     GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
                                      szBuffer) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }




 SYSVALS2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP04\SYSVALS2.C

 /*---------------------------------------------------
    SYSVALS2.C -- System Values Display Program No. 2
   ---------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "sysvals.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "SysVals2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HWND  hwndVscroll ;
      static SHORT cxChar, cxCaps, cyChar, cyDesc,
                   sVscrollPos, cxClient, cyClient ;
      CHAR         szBuffer [10] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sLine ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                cxChar = (SHORT) fm.lAveCharWidth ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;

                hwndVscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_VERTSCROLL) ;

                WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
                                    MPFROM2SHORT (sVscrollPos, 0),
                                    MPFROM2SHORT (0, NUMLINES - 1)) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                          sVscrollPos -= 1 ;
                          break ;

                     case SB_LINEDOWN:
                          sVscrollPos += 1 ;
                          break ;

                     case SB_PAGEUP:
                          sVscrollPos -= cyClient / cyChar ;
                          break ;

                     case SB_PAGEDOWN:
                          sVscrollPos += cyClient / cyChar ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sVscrollPos = SHORT1FROMMP (mp2) ;
                          break ;
                     }
                sVscrollPos = max (0, min (sVscrollPos, NUMLINES - 1)) ;

                if (sVscrollPos != SHORT1FROMMR (WinSendMsg (hwndVscroll,
                                        SBM_QUERYPOS, NULL, NULL)))
                     {
                     WinSendMsg (hwndVscroll, SBM_SETPOS,
                                 MPFROMSHORT (sVscrollPos), NULL) ;
                     WinInvalidateRect (hwnd, NULL, FALSE) ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;

                for (sLine = 0 ; sLine < NUMLINES ; sLine++)
                     {
                     ptl.x = cxCaps ;
                     ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
                                      + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szIdentifier),
                               sysvals[sLine].szIdentifier) ;

                     ptl.x += 20 * cxCaps ;
                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szDescription),
                               sysvals[sLine].szDescription) ;

                     ltoa (WinQuerySysValue (HWND_DESKTOP,
                                sysvals[sLine].sIndex), szBuffer, 10) ;

                     ptl.x += 38 * cxChar ;
                     GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
                                      szBuffer) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }



 SYSVALS3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP04\SYSVALS3.C

 /*---------------------------------------------------
    SYSVALS3.C -- System Values Display Program No. 3
   ---------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdlib.h>
 #include <string.h>
 #include "sysvals.h"

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "SysVals3" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL    | FCF_HORZSCROLL ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText)
      {
      POINTL aptlTextBox[TXTBOX_COUNT] ;

      GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;

      pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;

      return GpiCharStringAt (hps, pptl, lLength, pchText) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static HWND  hwndHscroll, hwndVscroll ;
      static SHORT sHscrollMax, sVscrollMax, sHscrollPos, sVscrollPos,
                   cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient,
                   cxTextTotal ;
      CHAR         szBuffer [10] ;
      FONTMETRICS  fm ;
      HPS          hps ;
      POINTL       ptl ;
      SHORT        sLine, sPaintBeg, sPaintEnd, sHscrollInc, sVscrollInc ;
      RECTL        rclInvalid ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;

                cxChar = (SHORT) fm.lAveCharWidth ;
                cxCaps = (SHORT) fm.lEmInc ;
                cyChar = (SHORT) fm.lMaxBaselineExt ;
                cyDesc = (SHORT) fm.lMaxDescender ;

                WinReleasePS (hps) ;

                cxTextTotal = 28 * cxCaps + 38 * cxChar ;

                hwndHscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_HORZSCROLL) ;

                hwndVscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_VERTSCROLL) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                sHscrollMax = max (0, cxTextTotal - cxClient) ;
                sHscrollPos = min (sHscrollPos, sHscrollMax) ;

                WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sHscrollPos, 0),
                                         MPFROM2SHORT (0, sHscrollMax)) ;

                WinEnableWindow (hwndHscroll, sHscrollMax ? TRUE : FALSE) ;

                sVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
                sVscrollPos = min (sVscrollPos, sVscrollMax) ;

                WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sVscrollPos, 0),
                                         MPFROM2SHORT (0, sVscrollMax)) ;

                WinEnableWindow (hwndVscroll, sVscrollMax ? TRUE : FALSE) ;
                return 0 ;

           case WM_HSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINELEFT:
                          sHscrollInc = -cxCaps ;
                          break ;

                     case SB_LINERIGHT:
                          sHscrollInc = cxCaps ;
                          break ;

                     case SB_PAGELEFT:
                          sHscrollInc = -8 * cxCaps ;
                          break ;

                     case SB_PAGERIGHT:
                          sHscrollInc = 8 * cxCaps ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sHscrollInc = SHORT1FROMMP (mp2) - sHscrollPos;
                          break ;

                     default:
                          sHscrollInc = 0 ;
                          break ;
                     }
                sHscrollInc = max (-sHscrollPos,
                              min (sHscrollInc, sHscrollMax - sHscrollPos)) ;

                if (sHscrollInc != 0)
                     {
                     sHscrollPos += sHscrollInc ;
                     WinScrollWindow (hwnd, -sHscrollInc, 0,
                                    NULL, NULL, NULL, NULL, SW_INVALIDATERGN)

                     WinSendMsg (hwndHscroll, SBM_SETPOS,
                                 MPFROMSHORT (sHscrollPos), NULL) ;
                     }
                return 0 ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                          sVscrollInc = -1 ;
                          break ;

                     case SB_LINEDOWN:
                          sVscrollInc = 1 ;
                          break ;

                     case SB_PAGEUP:
                          sVscrollInc = min (-1, -cyClient / cyChar) ;
                          break ;

                     case SB_PAGEDOWN:
                          sVscrollInc = max (1, cyClient / cyChar) ;
                          break ;

                     case SB_SLIDERTRACK:
                          sVscrollInc = SHORT1FROMMP (mp2) - sVscrollPos;
                          break ;

                     default:
                          sVscrollInc = 0 ;
                          break ;
                     }
                sVscrollInc = max (-sVscrollPos,
                              min (sVscrollInc, sVscrollMax - sVscrollPos)) ;

                if (sVscrollInc != 0)
                     {
                     sVscrollPos += sVscrollInc ;
                     WinScrollWindow (hwnd, 0, cyChar * sVscrollInc,
                                    NULL, NULL, NULL, NULL, SW_INVALIDATERGN)

                     WinSendMsg (hwndVscroll, SBM_SETPOS,
                                 MPFROMSHORT (sVscrollPos), NULL) ;

                     WinUpdateWindow (hwnd) ;
                     }
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
                GpiErase (hps) ;

                sPaintBeg = max (0, sVscrollPos +
                               (cyClient - (SHORT) rclInvalid.yTop) / cyChar)
                sPaintEnd = min (NUMLINES, sVscrollPos +
                               (cyClient - (SHORT) rclInvalid.yBottom)
                                    / cyChar + 1) ;

                for (sLine = sPaintBeg ; sLine < sPaintEnd ; sLine++)
                     {
                     ptl.x = cxCaps - sHscrollPos ;
                     ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
                                      + cyDesc ;

                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szIdentifier),
                               sysvals[sLine].szIdentifier) ;

                     ptl.x += 20 * cxCaps ;
                     GpiCharStringAt (hps, &ptl,
                               (LONG) strlen (sysvals[sLine].szDescription),
                               sysvals[sLine].szDescription) ;

                     ltoa (WinQuerySysValue (HWND_DESKTOP,
                                sysvals[sLine].sIndex), szBuffer, 10) ;

                     ptl.x += 38 * cxChar + 6 * cxCaps ;
                     RtJustCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
                                         szBuffer) ;
                     }
                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }



 SYSVALS4.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP07\SYSVALS4.C

 /*--------------------------------------------------------
    SYSVALS4.C -- System Values Display Program using AVIO
   --------------------------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #define INCL_VIO
 #define INCL_AVIO
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "sysvals.h"

 #define MAXWIDTH 60

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "SysVals4" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_VERTSCROLL    | FCF_HORZSCROLL ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 BYTE RgbToVioColor (COLOR clrRgb)
      {
      BYTE bIrgb ;
      RGB  rgb ;

      rgb = MAKETYPE (clrRgb, RGB) ;

      if (rgb.bBlue  >= 0x80) bIrgb |= '\x01' ;
      if (rgb.bGreen >= 0x80) bIrgb |= '\x02' ;
      if (rgb.bRed   >= 0x80) bIrgb |= '\x04' ;

      if (rgb.bBlue >= 0xC0 || rgb.bGreen >= 0xC0 || rgb.bRed >= 0xC0)
           bIrgb |= 8 ;

      if (bIrgb == 0 && rgb.bBlue >= 0x40 && rgb.bGreen >= 0x40 &&
                        rgb.bRed  >= 0x40)
           bIrgb = 8 ;

      return bIrgb ;
      }

 BYTE ConstructDefaultAttribute (VOID)
      {
      return RgbToVioColor (
                  WinQuerySysColor (HWND_DESKTOP, SYSCLR_WINDOW, 0L)) << 4 |
             RgbToVioColor (
                  WinQuerySysColor (HWND_DESKTOP, SYSCLR_WINDOWTEXT, 0L)) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BYTE   bBlankCell [2] = " " ;
      static HVPS   hvps ;
      static HWND   hwndHscroll, hwndVscroll ;
      static HPS    hps ;
      static SHORT  sHscrollPos, sVscrollPos,
                    cxChar, cyChar, cxClient, cyClient ;
      CHAR          szBuffer [80] ;
      HDC           hdc ;
      SIZEL         sizl ;
      USHORT        usRow ;

      switch (msg)
           {
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                sizl.cx = sizl.cy = 0 ;
                hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAULT
                                                    GPIT_MICRO | GPIA_ASSOC) ;

                VioCreatePS (&hvps, NUMLINES, MAXWIDTH, 0, 1, NULL) ;
                VioAssociate (hdc, hvps) ;
                VioGetDeviceCellSize (&cyChar, &cxChar, hvps) ;

                bBlankCell[1] = ConstructDefaultAttribute () ;
                VioScrollUp (0, 0, -1, -1, -1, bBlankCell, hvps) ;

                for (usRow = 0 ; usRow < NUMLINES ; usRow++)
                     VioWrtCharStr (szBuffer,
                                    sprintf (szBuffer, "%-20s%-35s%5ld",
                                             sysvals[usRow].szIdentifier,
                                             sysvals[usRow].szDescription,
                                             WinQuerySysValue (HWND_DESKTOP,
                                             sysvals[usRow].sIndex)),
                                    usRow, 0, hvps) ;

                hwndHscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_HORZSCROLL) ;

                WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sHscrollPos, 0),
                                         MPFROM2SHORT (0, MAXWIDTH - 1)) ;

                hwndVscroll = WinWindowFromID (
                                    WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                    FID_VERTSCROLL) ;

                WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
                                         MPFROM2SHORT (sVscrollPos, 0),
                                         MPFROM2SHORT (0, NUMLINES - 1)) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
                return 0 ;

           case WM_HSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINELEFT:
                          sHscrollPos -= 1 ;
                          break ;

                     case SB_LINERIGHT:
                          sHscrollPos += 1 ;
                          break ;

                     case SB_PAGELEFT:
                          sHscrollPos -= 8 ;
                          break ;

                     case SB_PAGERIGHT:
                          sHscrollPos += 8 ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sHscrollPos = SHORT1FROMMP (mp2) ;
                          break ;
                     }
                sHscrollPos = max (0, min (sHscrollPos, MAXWIDTH - 1)) ;

                if (sHscrollPos != SHORT1FROMMR (WinSendMsg (hwndHscroll,
                                        SBM_QUERYPOS, NULL, NULL)))
                     {
                     VioSetOrg (sVscrollPos, sHscrollPos, hvps) ;

                     WinSendMsg (hwndHscroll, SBM_SETPOS,
                                 MPFROM2SHORT (sHscrollPos, 0), NULL) ;
                     }
                return 0 ;

           case WM_VSCROLL:
                switch (SHORT2FROMMP (mp2))
                     {
                     case SB_LINEUP:
                          sVscrollPos -= 1 ;
                          break ;

                     case SB_LINEDOWN:
                          sVscrollPos += 1 ;
                          break ;

                     case SB_PAGEUP:
                          sVscrollPos -= cyClient / cyChar ;
                          break ;

                     case SB_PAGEDOWN:
                          sVscrollPos += cyClient / cyChar ;
                          break ;

                     case SB_SLIDERPOSITION:
                          sVscrollPos = SHORT1FROMMP (mp2) ;
                          break ;
                     }
                sVscrollPos = max (0, min (sVscrollPos, NUMLINES - 1)) ;

                if (sVscrollPos != SHORT1FROMMR (WinSendMsg (hwndVscroll,
                                        SBM_QUERYPOS, NULL, NULL)))
                     {
                     VioSetOrg (sVscrollPos, sHscrollPos, hvps) ;

                     WinSendMsg (hwndVscroll, SBM_SETPOS,
                                 MPFROM2SHORT (sVscrollPos, 0), NULL) ;
                     }
                return 0 ;

           case WM_CHAR:
                switch (CHARMSG(&msg)->vkey)
                     {
                     case VK_LEFT:
                     case VK_RIGHT:
                          return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
                     case VK_UP:
                     case VK_DOWN:
                     case VK_PAGEUP:
                     case VK_PAGEDOWN:
                          return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
                     }
                break ;

           case WM_PAINT:
                WinBeginPaint (hwnd, hps, NULL) ;
                GpiErase (hps) ;

                VioShowBuf (0, MAXWIDTH * NUMLINES * 2, hvps) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                VioAssociate (NULL, hvps) ;
                VioDestroyPS (hvps) ;
                GpiDestroyPS (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 TYPEAWAY.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP08\TYPEAWAY.C

 /*------------------------------
    TYPEAWAY.C -- Typing Program
   ------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "easyfont.h"

 #define LCID_FIXEDFONT 1L
 #define BUFFER(x,y) (*(pBuffer + y * xMax + x))

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 CHAR szClientClass [] = "TypeAway" ;
 HAB  hab ;

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;
      if (hwndFrame != NULL)
           {
           WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
                WinDispatchMsg (hab, &qmsg) ;

           WinDestroyWindow (hwndFrame) ;
           }
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID GetCharXY (HPS hps, SHORT *pcxChar, SHORT *pcyChar, SHORT *pcyDesc)
      {
      FONTMETRICS fm ;

      GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
      *pcxChar = (SHORT) fm.lAveCharWidth ;
      *pcyChar = (SHORT) fm.lMaxBaselineExt ;
      *pcyDesc = (SHORT) fm.lMaxDescender ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fInsertMode = FALSE ;
      static CHAR  *pBuffer ;
      static SHORT cxClient, cyClient, cxChar, cyChar, cyDesc,
                   xCursor, yCursor, xMax,  yMax ;
      BOOL         fProcessed ;
      CHAR         szBuffer [20] ;
      HPS          hps ;
      POINTL       ptl ;
      RECTL        rcl ;
      SHORT        sRep, s ;

      switch (msg)
           {
           case WM_CREATE:
                hps = WinGetPS (hwnd) ;
                EzfQueryFonts (hps) ;

                if (!EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                                            FONTSIZE_10, 0))
                     {
                     WinReleasePS (hps) ;

                     WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                          "Cannot find a fixed-pitch font.  Load the Courier "
                          "fonts from the Control Panel and try again.",
                          szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;

                     return 1 ;
                     }

                GpiSetCharSet (hps, LCID_FIXEDFONT) ;

                GetCharXY (hps, &cxChar, &cyChar, &cyDesc) ;

                GpiSetCharSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                WinReleasePS (hps) ;
                return 0 ;

           case WM_SIZE:
                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                xMax = min (255, cxClient / cxChar) ;
                yMax = min (255, cyClient / cyChar - 2) ;

                if (pBuffer != NULL)
                     free (pBuffer) ;

                if (NULL == (pBuffer = malloc (xMax * yMax + 1)))
                     {
                     WinMessageBox (HWND_DESKTOP, hwnd,
                          "Cannot allocate memory for text buffer.\n"
                          "Try a smaller window.", szClientClass, 0,
                          MB_OK | MB_ICONEXCLAMATION) ;

                     xMax = yMax = 0 ;
                     }
                else
                     {
                     for (s = 0 ; s < xMax * yMax ; BUFFER (s++, 0) = ' ') ;

                     xCursor = 0 ;
                     yCursor = 0 ;
                     }

                if (hwnd == WinQueryFocus (HWND_DESKTOP, FALSE))
                     {
                     WinDestroyCursor (hwnd) ;

                     WinCreateCursor (hwnd, 0, cyClient - cyChar,
                                      cxChar, cyChar,
                                      CURSOR_SOLID | CURSOR_FLASH, NULL) ;

                     WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
                     }
                return 0 ;

           case WM_SETFOCUS:
                if (SHORT1FROMMP (mp2))
                     {
                     WinCreateCursor (hwnd, cxChar * xCursor,
                                      cyClient - cyChar * (1 + yCursor),
                                      cxChar, cyChar,
                                      CURSOR_SOLID | CURSOR_FLASH, NULL) ;

                     WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
                     }
                else
                     WinDestroyCursor (hwnd) ;
                return 0 ;

           case WM_CHAR:
                if (xMax == 0 || yMax == 0)
                     return 0 ;

                if (CHARMSG(&msg)->fs & KC_KEYUP)
                     return 0 ;

                if (CHARMSG(&msg)->fs & KC_INVALIDCHAR)
                     return 0 ;

                if (CHARMSG(&msg)->fs & KC_INVALIDCOMP)
                     {
                     xCursor = (xCursor + 1) % xMax ;        // Advance cursor
                     if (xCursor == 0)
                          yCursor = (yCursor + 1) % yMax ;

                     WinAlarm (HWND_DESKTOP, WA_ERROR) ;     // And beep
                     }

                for (sRep = 0 ; sRep < CHARMSG(&msg)->cRepeat ; sRep++)
                     {
                     fProcessed = FALSE ;

                     ptl.x = xCursor * cxChar ;
                     ptl.y = cyClient - cyChar * (yCursor + 1) + cyDesc ;

                               /*---------------------------
                                  Process some virtual keys
                                 ---------------------------*/

                     if (CHARMSG(&msg)->fs & KC_VIRTUALKEY)
                          {
                          fProcessed = TRUE ;

                          switch (CHARMSG(&msg)->vkey)
                               {
                                         /*---------------
                                            Backspace key
                                           ---------------*/

                               case VK_BACKSPACE:
                                    if (xCursor > 0)
                                         {
                                         WinSendMsg (hwnd, WM_CHAR,
                                              MPFROM2SHORT (KC_VIRTUALKEY, 1),
                                              MPFROM2SHORT (0, VK_LEFT)) ;

                                         WinSendMsg (hwnd, WM_CHAR,
                                              MPFROM2SHORT (KC_VIRTUALKEY, 1),
                                              MPFROM2SHORT (0, VK_DELETE)) ;
                                         }
                                    break ;

                                         /*---------
                                            Tab key
                                           ---------*/

                               case VK_TAB:
                                    s = min (8 - xCursor % 8, xMax - xCursor)

                                    WinSendMsg (hwnd, WM_CHAR,
                                         MPFROM2SHORT (KC_CHAR, s),
                                         MPFROM2SHORT ((USHORT) ' ', 0)) ;
                                    break ;

                                         /*-------------------------
                                            Backtab (Shift-Tab) key
                                           -------------------------*/

                               case VK_BACKTAB:
                                    if (xCursor > 0)
                                         {
                                         s = (xCursor - 1) % 8 + 1 ;

                                         WinSendMsg (hwnd, WM_CHAR,
                                              MPFROM2SHORT (KC_VIRTUALKEY, s),
                                              MPFROM2SHORT (0, VK_LEFT)) ;
                                         }
                                    break ;

                                         /*------------------------
                                            Newline and Enter keys
                                           ------------------------*/

                               case VK_NEWLINE:
                               case VK_ENTER:
                                    xCursor = 0 ;
                                    yCursor = (yCursor + 1) % yMax ;
                                    break ;

                               default:
                                    fProcessed = FALSE ;
                                    break ;
                               }
                          }

                               /*------------------------
                                  Process character keys
                                 ------------------------*/

                     if (!fProcessed && CHARMSG(&msg)->fs & KC_CHAR)
                          {
                                                   // Shift line if fInsertMod
                          if (fInsertMode)
                               for (s = xMax - 1 ; s > xCursor ; s--)
                                    BUFFER (s, yCursor) =
                                         BUFFER (s - 1, yCursor) ;

                                                   // Store character in buffe

                          BUFFER (xCursor, yCursor) =
                                              (CHAR) CHARMSG(&msg)->chr ;

                                                   // Display char or new line

                          WinShowCursor (hwnd, FALSE) ;
                          hps = WinGetPS (hwnd) ;

                          EzfCreateLogFont (hps, LCID_FIXEDFONT,
                                            FONTFACE_COUR, FONTSIZE_10, 0) ;
                          GpiSetCharSet (hps, LCID_FIXEDFONT) ;
                          GpiSetBackMix (hps, BM_OVERPAINT) ;

                          if (fInsertMode)
                               GpiCharStringAt (hps, &ptl,
                                                (LONG) (xMax - xCursor),
                                                & BUFFER (xCursor, yCursor)) ;
                          else
                               GpiCharStringAt (hps, &ptl, 1L,
                                                (CHAR *) & CHARMSG(&msg)->chr)

                          GpiSetCharSet (hps, LCID_DEFAULT) ;
                          GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                          WinReleasePS (hps) ;
                          WinShowCursor (hwnd, TRUE) ;

                                                   // Increment cursor

                          if (!(CHARMSG(&msg)->fs & KC_DEADKEY))
                               if (0 == (xCursor = (xCursor + 1) % xMax))
                                    yCursor = (yCursor + 1) % yMax ;

                          fProcessed = TRUE ;
                          }

                               /*--------------------------------
                                  Process remaining virtual keys
                                 --------------------------------*/

                     if (!fProcessed && CHARMSG(&msg)->fs & KC_VIRTUALKEY)
                          {
                          fProcessed = TRUE ;

                          switch (CHARMSG(&msg)->vkey)
                               {
                                         /*----------------------
                                            Cursor movement keys
                                           ----------------------*/

                               case VK_LEFT:
                                    xCursor = (xCursor - 1 + xMax) % xMax ;

                                    if (xCursor == xMax - 1)
                                         yCursor = (yCursor - 1 + yMax) % yMax
                                    break ;

                               case VK_RIGHT:
                                    xCursor = (xCursor + 1) % xMax ;

                                    if (xCursor == 0)
                                         yCursor = (yCursor + 1) % yMax ;
                                    break ;

                               case VK_UP:
                                    yCursor = max (yCursor - 1, 0) ;
                                    break ;

                               case VK_DOWN:
                                    yCursor = min (yCursor + 1, yMax - 1) ;
                                    break ;

                               case VK_PAGEUP:
                                    yCursor = 0 ;
                                    break ;

                               case VK_PAGEDOWN:
                                    yCursor = yMax - 1 ;
                                    break ;

                               case VK_HOME:
                                    xCursor = 0 ;
                                    break ;

                               case VK_END:
                                    xCursor = xMax - 1 ;
                                    break ;

                                         /*------------
                                            Insert key
                                           ------------*/

                               case VK_INSERT:
                                    fInsertMode = fInsertMode ? FALSE : TRUE ;
                                    WinSetRect (hab, &rcl, 0, 0,
                                                cxClient, cyChar) ;
                                    WinInvalidateRect (hwnd, &rcl, FALSE) ;
                                    break ;

                                         /*------------
                                            Delete key
                                           ------------*/

                               case VK_DELETE:
                                    for (s = xCursor ; s < xMax - 1 ; s++)
                                         BUFFER (s, yCursor) =
                                              BUFFER (s + 1, yCursor) ;

                                    BUFFER (xMax, yCursor) = ' ' ;

                                    WinShowCursor (hwnd, FALSE) ;
                                    hps = WinGetPS (hwnd) ;
                                    EzfCreateLogFont (hps, LCID_FIXEDFONT,
                                              FONTFACE_COUR, FONTSIZE_10, 0) ;
                                    GpiSetCharSet (hps, LCID_FIXEDFONT) ;
                                    GpiSetBackMix (hps, BM_OVERPAINT) ;

                                    GpiCharStringAt (hps, &ptl,
                                              (LONG) (xMax - xCursor),
                                              & BUFFER (xCursor, yCursor)) ;

                                    GpiSetCharSet (hps, LCID_DEFAULT) ;
                                    GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                                    WinReleasePS (hps) ;
                                    WinShowCursor (hwnd, TRUE) ;
                                    break ;

                               default:
                                    fProcessed = FALSE ;
                                    break ;
                               }
                          }
                     }
                WinCreateCursor (hwnd, cxChar * xCursor,
                                       cyClient - cyChar * (1 + yCursor),
                                       0, 0, CURSOR_SETPOS, NULL) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;
                EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
                                                       FONTSIZE_10, 0) ;
                GpiSetCharSet (hps, LCID_FIXEDFONT) ;

                ptl.x = cxChar ;
                ptl.y = cyDesc ;
                GpiCharStringAt (hps, &ptl,
                                 (LONG) sprintf (szBuffer, "Insert Mode: %s",
                                                 fInsertMode ? "ON" : "OFF"),
                                 szBuffer) ;

                ptl.x = 0 ;
                ptl.y = 3 * cyChar / 2 ;
                GpiMove (hps, &ptl) ;

                ptl.x = cxClient ;
                GpiLine (hps, &ptl) ;

                if (xMax > 0 && yMax > 0)
                     {
                     for (s = 0 ; s < yMax ; s++)
                          {
                          ptl.x = 0 ;
                          ptl.y = cyClient - cyChar * (s + 1) + cyDesc ;

                          GpiCharStringAt (hps, &ptl, (LONG) xMax,
                                                      & BUFFER (0, s)) ;
                          }
                     }
                GpiSetCharSet (hps, LCID_DEFAULT) ;
                GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                if (pBuffer != NULL)
                     free (pBuffer) ;
                break ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 TYPECLIP.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP15\TYPECLIP.C

 /*--------------------------------------------
    TYPECLIP.C -- Clipboard Text Demonstration
   --------------------------------------------*/

 #define INCL_WIN
 #define INCL_VIO
 #define INCL_AVIO
 #include <os2.h>
 #include <stdlib.h>
 #include "typeclip.h"

 #define WM_ADJUST_ORG     (WM_USER + 0)
 #define WM_SET_BLOCKOUT   (WM_USER + 1)
 #define WM_CLEAR_BLOCKOUT (WM_USER + 2)

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 HAB  hab ;

 int main (void)
      {
      static CHAR  szClientClass [] = "TypeClip" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST |
                                  FCF_MENU          | FCF_ACCELTABLE ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, ID_RESOURCE, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static BOOL  fCapture, fValidBlock ;
      static HPS   hps ;
      static HVPS  hvps ;
      static HWND  hwndMenu ;
      static LONG  cxChar, cyChar ;
      static SHORT cxClient, cyClient, cxVioSize, cyVioSize,
                   xBlockBeg, yBlockBeg, xBlockEnd, yBlockEnd,
                   xBeg , yBeg, xEnd, yEnd ;
      CHAR         *pchText ;
      HDC          hdc ;
      PCHAR        pchClipText ;
      RECTL        rcl ;
      SEL          selClipText ;
      SHORT        sRep, sLen, x, y, s,
                   xCursor, yCursor, xOrigin, yOrigin ;
      SIZEL        sizl ;
      USHORT       usfInfo ;

      switch (msg)
           {
                               /*---------------------------------------
                                  Create, paint, and destroy processing
                                 ---------------------------------------*/
           case WM_CREATE:
                hdc = WinOpenWindowDC (hwnd) ;

                DevQueryCaps (hdc, CAPS_CHAR_WIDTH,  1L, &cxChar) ;
                DevQueryCaps (hdc, CAPS_CHAR_HEIGHT, 1L, &cyChar) ;

                cxVioSize = (SHORT) (WinQuerySysValue (HWND_DESKTOP,
                                         SV_CXFULLSCREEN) / cxChar) ;

                cyVioSize = (SHORT) (WinQuerySysValue (HWND_DESKTOP,
                                         SV_CYFULLSCREEN) / cyChar) ;

                sizl.cx = sizl.cy = 0 ;
                hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAULT
                                                    GPIT_MICRO | GPIA_ASSOC) ;

                VioCreatePS (&hvps, cyVioSize, cxVioSize, 0, 1, NULL) ;
                VioAssociate (hdc, hvps) ;

                hwndMenu = WinWindowFromID (
                                 WinQueryWindow (hwnd, QW_PARENT, FALSE),
                                 FID_MENU) ;
                return 0 ;

           case WM_PAINT:
                WinBeginPaint (hwnd, hps, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;
                WinFillRect (hps, &rcl, CLR_BLACK) ;

                VioShowBuf (0, cxVioSize * cyVioSize * 2, hvps) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                VioAssociate (NULL, hvps) ;
                VioDestroyPS (hvps) ;
                GpiDestroyPS (hps) ;
                return 0 ;
                               /*-------------------------------------------
                                  Window size, keyboard and origin handling
                                 -------------------------------------------*/
           case WM_SIZE:
                WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;

                cxClient = SHORT1FROMMP (mp2) ;
                cyClient = SHORT2FROMMP (mp2) ;

                VioSetOrg (0, 0, hvps) ;
                WinSendMsg (hwnd, WM_ADJUST_ORG, NULL, NULL) ;
                return 0 ;

           case WM_CHAR:
                if (!(CHARMSG(&msg)->fs & KC_CHAR)        ||
                     (CHARMSG(&msg)->fs & KC_KEYUP)       ||
                     (CHARMSG(&msg)->fs & KC_INVALIDCHAR) ||
                     (CHARMSG(&msg)->fs & KC_DEADKEY))
                          return 0 ;

                for (sRep = 0 ; sRep < CHARMSG(&msg)->cRepeat ; sRep++)
                     {
                     VioWrtTTY ((PCHAR) & CHARMSG(&msg)->chr, 1, hvps) ;

                     switch (CHARMSG(&msg)->chr)
                          {
                          case '\b':                         // Backspace
                               VioWrtTTY (" \b", 2, hvps) ;
                               break ;

                          case '\r':                         // Return
                               VioWrtTTY ("\n", 1, hvps) ;
                               break ;
                          }
                     }
                WinSendMsg (hwnd, WM_ADJUST_ORG, NULL, NULL) ;
                return 1 ;

           case WM_ADJUST_ORG:
                VioGetOrg    (&yOrigin, &xOrigin, hvps) ;
                VioGetCurPos (&yCursor, &xCursor, hvps) ;

                if (xCursor < xOrigin)
                     xOrigin = xCursor ;

                else if (xCursor >= xOrigin + cxClient / (SHORT) cxChar)
                     xOrigin = xCursor - cxClient / (SHORT) cxChar + 1 ;

                if (yCursor < yOrigin)
                     yOrigin = yCursor ;

                else if (yCursor >= yOrigin + cyClient / (SHORT) cyChar)
                     yOrigin = yCursor - cyClient / (SHORT) cyChar + 1 ;

                VioSetOrg (yOrigin, xOrigin, hvps) ;
                return 0 ;
                               /*-----------------------------
                                  Mouse and blockout handling
                                 -----------------------------*/
           case WM_BUTTON1DOWN:
                x = MOUSEMSG(&msg)->x / (SHORT) cxChar ;
                y = (cyClient - MOUSEMSG(&msg)->y) / (SHORT) cyChar ;

                WinSendMsg (hwnd, WM_CLEAR_BLOCKOUT, NULL, NULL) ;

                WinSetCapture (HWND_DESKTOP, hwnd) ;
                fCapture = TRUE ;
                fValidBlock = FALSE ;

                VioGetOrg (&yOrigin, &xOrigin, hvps) ;

                xBlockBeg = xOrigin + x ;
                yBlockBeg = yOrigin + y ;

                WinSendMsg (hwnd, WM_SET_BLOCKOUT, mp1, mp2) ;
                break ;

           case WM_MOUSEMOVE:
                if (fCapture)
                     WinSendMsg (hwnd, WM_SET_BLOCKOUT, mp1, mp2) ;
                break ;

           case WM_BUTTON1UP:
                if (fCapture)
                     {
                     WinSetCapture (HWND_DESKTOP, NULL) ;
                     fCapture = FALSE ;
                     fValidBlock = TRUE ;

                     WinSendMsg (hwnd, WM_SET_BLOCKOUT, mp1, mp2) ;
                     }
                return 1 ;

           case WM_SET_BLOCKOUT:
                x = MOUSEMSG(&msg)->x / (SHORT) cxChar ;
                y = (cyClient - MOUSEMSG(&msg)->y) / (SHORT) cyChar ;

                VioGetOrg (&yOrigin, &xOrigin, hvps) ;

                xBlockEnd = xOrigin + x ;
                yBlockEnd = yOrigin + y ;

                if (cxVioSize * yBlockBeg + xBlockBeg <
                    cxVioSize * yBlockEnd + xBlockEnd)
                     {
                     xBeg = xBlockBeg ;
                     yBeg = yBlockBeg ;
                     xEnd = xBlockEnd ;
                     yEnd = yBlockEnd ;
                     }
                else
                     {
                     xBeg = xBlockEnd ;
                     yBeg = yBlockEnd ;
                     xEnd = xBlockBeg ;
                     yEnd = yBlockBeg ;
                     }

                VioWrtNAttr ("\x07", cxVioSize * yBeg + xBeg, 0, 0, hvps) ;

                VioWrtNAttr ("\x70",  cxVioSize * yEnd + xEnd -
                                     (cxVioSize * yBeg + xBeg) + 1,
                                     yBeg, xBeg, hvps) ;

                VioWrtNAttr ("\x07",  cxVioSize * cyVioSize -
                                     (cxVioSize * yEnd + xEnd) - 1,
                                     yEnd, xEnd + 1, hvps) ;
                break ;

           case WM_CLEAR_BLOCKOUT:
                VioWrtNAttr ("\x07", cxVioSize * cyVioSize, 0, 0, hvps) ;
                fValidBlock = FALSE ;
                break ;
                               /*--------------------
                                  Clipboard handling
                                 --------------------*/
           case WM_INITMENU:
                switch (SHORT1FROMMP (mp1))
                     {
                     case IDM_EDIT:
                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_CUT, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                                           fValidBlock ? 0 : MIA_DISABLED)) ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_COPY, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                                           fValidBlock ? 0 : MIA_DISABLED)) ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_CLEAR, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                                           fValidBlock ? 0 : MIA_DISABLED)) ;

                          WinSendMsg (hwndMenu, MM_SETITEMATTR,
                                      MPFROM2SHORT (IDM_PASTE, TRUE),
                                      MPFROM2SHORT (MIA_DISABLED,
                               WinQueryClipbrdFmtInfo (hab, CF_TEXT, &usfInfo)
                                              ? 0 : MIA_DISABLED)) ;
                          return 0 ;
                     }
                break ;

           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case IDM_CUT:
                     case IDM_COPY:
                          if (!fValidBlock)
                               return 0 ;
                                                        // Copy text to
                                                        // malloc'ed memory

                          sLen = (cxVioSize + 2) * (yEnd - yBeg + 1) + 1 ;
                          pchText = malloc (sLen) ;

                          for (s = 0, y = yBeg ; y <= yEnd ; y++)
                               {
                               x    = (y == yBeg ? xBeg : 0) ;
                               sLen = (y == yEnd ? xEnd + 1 : cxVioSize) - x ;

                               VioReadCharStr (pchText + s, &sLen,
                                               y, x, hvps) ;

                               s += sLen ;
                               while (--s >= 0 && pchText[s] == ' ') ;

                               s++ ;
                               pchText[s++] = '\r' ;
                               pchText[s++] = '\n' ;
                               }
                          pchText[s++] = '\0' ;         // s is string length

                                                        // Allocate memory blo

                          DosAllocSeg (s, &selClipText, SEG_GIVEABLE) ;
                          pchClipText = MAKEP (selClipText, 0) ;

                                                        // Copy to giveable se

                          for (s = 0 ; pchClipText[s] = pchText[s] ; s++) ;
                          free (pchText) ;
                                                        // Set clipboard data
                          WinOpenClipbrd (hab) ;
                          WinEmptyClipbrd (hab) ;
                          WinSetClipbrdData (hab, (ULONG) selClipText,
                                                  CF_TEXT, CFI_SELECTOR) ;
                          WinCloseClipbrd (hab) ;
                                                        // Clear blockout

                          if (COMMANDMSG(&msg)->cmd == IDM_COPY)
                               {
                               WinSendMsg (hwnd, WM_CLEAR_BLOCKOUT, NULL, NULL
                               return 0 ;
                               }
                                              // fall through for IDM_CUT
                     case IDM_CLEAR:
                          if (!fValidBlock)
                               return 0 ;
                                                        // Clear selection

                          VioWrtNCell (" \x07",  cxVioSize * yEnd + xEnd -
                                                (cxVioSize * yBeg + xBeg) + 1,
                                                yBeg, xBeg, hvps) ;
                          fValidBlock = FALSE ;
                          return 0 ;

                     case IDM_PASTE:
                                         // Get text selector from clipboard

                          WinOpenClipbrd (hab) ;
                          selClipText = (SEL) WinQueryClipbrdData (hab,
                                                                   CF_TEXT) ;
                                         // Display to screen

                          if (selClipText != 0)
                               {
                               pchClipText = MAKEP (selClipText, 0) ;

                               for (sLen = 0 ; pchClipText[sLen] ; sLen++) ;

                               VioWrtTTY (pchClipText, sLen, hvps) ;
                               }
                          WinCloseClipbrd (hab) ;

                          WinSendMsg (hwnd, WM_ADJUST_ORG, NULL, NULL) ;
                          return 0 ;
                     }
                break ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 W.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\W.C

 /*-----------------------------
    W.C -- A Do-Nothing Program
   -----------------------------*/

 int main (void)
      {
      return 0 ;
      }


 WE.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WE.C

 /*-------------------------------------------------------
    WE.C -- A Program that Obtains an Anchor Block Handle
   -------------------------------------------------------*/

 #include <os2.h>

 int main (void)
      {
      HAB  hab ;

      hab = WinInitialize (0) ;

      WinTerminate (hab) ;
      return 0 ;
      }


 WEB.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP09\WEB.C

 /*--------------------------------------
    WEB.C -- Mouse Movement Demo Program
   --------------------------------------*/

 #define INCL_WIN
 #define INCL_GPI
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Web" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (hab, szClientClass, ClientWndProc,
                        CS_SIZEREDRAW | CS_SYNCPAINT, 0) ;

      hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
                                      &flFrameFlags, szClientClass, NULL,
                                      0L, NULL, 0, &hwndClient) ;

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 VOID DrawWeb (HPS hps, POINTL *pptlPointerPos, POINTL *pptlClient)
      {
      POINTL ptl ;
                                    // Lower Left --> Pointer --> Upper Right
      ptl.x = 0 ;
      ptl.y = 0 ;
      GpiMove (hps, &ptl) ;
      GpiLine (hps, pptlPointerPos) ;
      GpiLine (hps, pptlClient) ;
                                    // Upper Left --> Pointer --> Lower Right
      ptl.x = 0 ;
      ptl.y = pptlClient->y ;
      GpiMove (hps, &ptl) ;
      GpiLine (hps, pptlPointerPos) ;

      ptl.x = pptlClient->x ;
      ptl.y = 0 ;
      GpiLine (hps, &ptl) ;
                                    // Lower Center --> Pointer --> Upper Cent
      ptl.x = pptlClient->x / 2 ;
      ptl.y = 0 ;
      GpiMove (hps, &ptl) ;
      GpiLine (hps, pptlPointerPos) ;

      ptl.y = pptlClient->y ;
      GpiLine (hps, &ptl) ;
                                    // Left Center --> Pointer --> Right Cente
      ptl.x = 0 ;
      ptl.y = pptlClient->y / 2 ;
      GpiMove (hps, &ptl) ;
      GpiLine (hps, pptlPointerPos) ;

      ptl.x = pptlClient->x ;
      GpiLine (hps, &ptl) ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static POINTL ptlClient, ptlPointerPos ;
      HPS           hps ;

      switch (msg)
           {
           case WM_SIZE:
                ptlClient.x = SHORT1FROMMP (mp2) ;
                ptlClient.y = SHORT2FROMMP (mp2) ;
                return 0 ;

           case WM_MOUSEMOVE:
                hps = WinGetPS (hwnd) ;
                GpiSetMix (hps, FM_INVERT) ;

                DrawWeb (hps, &ptlPointerPos, &ptlClient) ;

                ptlPointerPos.x = MOUSEMSG(&msg)->x ;
                ptlPointerPos.y = MOUSEMSG(&msg)->y ;

                DrawWeb (hps, &ptlPointerPos, &ptlClient) ;

                WinReleasePS (hps) ;
                break ;                       // do default processing

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;
                GpiErase (hps) ;
                GpiSetMix (hps, FM_INVERT) ;

                DrawWeb (hps, &ptlPointerPos, &ptlClient) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 WEL.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WEL.C

 /*-------------------------------------------------
    WEL.C -- A Program that Creates a Message Queue
   -------------------------------------------------*/

 #include <os2.h>

 int main (void)
      {
      HAB  hab ;
      HMQ  hmq ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }


 WELC.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WELC.C

 /*----------------------------------------------------------
    WELC.C -- A Program that Creates a Standard Frame Window
   ----------------------------------------------------------*/

 #include <os2.h>

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     NULL,               // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     NULL) ;             // Pointer to client window handle

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }


 WELCO.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WELCO.C

 /*------------------------------------------
    WELCO.C -- A Program with a Message Loop
   ------------------------------------------*/

 #include <os2.h>

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     NULL,               // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     NULL) ;             // Pointer to client window handle

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }


 WELCOM.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WELCOM.C

 /*------------------------------------------------
    WELCOM.C -- A Program that has a Standard Icon
   ------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 int main (void)
      {
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     NULL,               // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     NULL) ;             // Pointer to client window handle

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }


 WELCOME.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WELCOME.C

 /*-----------------------------------------------------
    WELCOME.C -- A Program that Creates a Client Window
   -----------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Welcome" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass,      // Name of class being registered
                     ClientWndProc,      // Window procedure for class
                     0L,                 // Class style
                     0) ;                // Extra bytes to reserve

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass,      // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient) ;      // Pointer to client window handle

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 WELCOME1.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP02\WELCOME1.C

 /*----------------------------------------------------------
    WELCOME1.C -- A Program that Writes to its Client Window
   ----------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Welcome1" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame, hwndClient ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass,      // Name of class being registered
                     ClientWndProc,      // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     0) ;                // Extra bytes to reserve

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass,      // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient) ;      // Pointer to client window handle

      WinSendMsg (hwndFrame, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR szText [] = "Welcome to the OS/2 Presentation Manager" ;
      HPS         hps;
      RECTL       rcl ;

      switch (msg)
           {
           case WM_CREATE:
                DosBeep (261, 100) ;
                DosBeep (330, 100) ;
                DosBeep (392, 100) ;
                DosBeep (523, 500) ;
                return 0 ;

           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGROUN
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_DESTROY:
                DosBeep (523, 100) ;
                DosBeep (392, 100) ;
                DosBeep (330, 100) ;
                DosBeep (261, 500) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 WELCOME2.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP03\WELCOME2.C

 /*------------------------------------------------------------
    WELCOME2.C -- A Program that Creates Two Top-Level Windows
   ------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY Client1WndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY Client2WndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass1 [] = "Welcome2.1",
                   szClientClass2 [] = "Welcome2.2" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
                                  FCF_SIZEBORDER    | FCF_MINMAX  |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame1, hwndFrame2, hwndClient1, hwndClient2 ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass1,     // Name of class being registered
                     Client1WndProc,     // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     0) ;                // Extra bytes to reserve

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass2,     // Name of class being registered
                     Client2WndProc,     // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     0) ;                // Extra bytes to reserve

      hwndFrame1 = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass1,     // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient1) ;     // Pointer to client window handle

      hwndFrame2 = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass2,     // Client window class name
                     " - Window No. 2",  // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient2) ;     // Pointer to client window handle

      WinSendMsg (hwndFrame1, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      WinSendMsg (hwndFrame2, WM_SETICON,
                  WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                  NULL) ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame1) ;
      WinDestroyWindow (hwndFrame2) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY Client1WndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
      {
      static CHAR szText [] = "Welcome to Window No. 1" ;
      HPS         hps ;
      RECTL       rcl ;

      switch (msg)
           {
           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGROUN
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY Client2WndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
      {
      static CHAR szText [] = "Welcome to Window No. 2" ;
      HPS         hps ;
      RECTL       rcl ;

      switch (msg)
           {
           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGROUN
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_CLOSE:
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 WELCOME3.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP03\WELCOME3.C

 /*-----------------------------------------------------------
    WELCOME3.C -- Creates a Top-Level Window and Two Children
   -----------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
 MRESULT EXPENTRY ChildWndProc  (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Welcome3",
                   szChildClass  [] = "Welcome3.Child" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
                                  FCF_SIZEBORDER    | FCF_MINMAX   |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame,  hwndChildFrame1,  hwndChildFrame2,
                   hwndClient, hwndChildClient1, hwndChildClient2 ;
      QMSG         qmsg ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass,      // Name of class being registered
                     ClientWndProc,      // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     0) ;                // Extra bytes to reserve

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szChildClass,       // Name of class being registered
                     ChildWndProc,       // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     sizeof (PVOID)) ;   // Extra bytes to reserve

           /*-------------------------
              Create top-level window
             -------------------------*/

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass,      // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient) ;      // Pointer to client window handle

      WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           /*--------------------------
              Create two child windows
             --------------------------*/

      flFrameFlags &= ~FCF_TASKLIST ;

      hwndChildFrame1 = WinCreateStdWindow (
                     hwndClient,         // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szChildClass,       // Client window class name
                     "Child No. 1",      // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndChildClient1) ;// Pointer to client window handle

      hwndChildFrame2 = WinCreateStdWindow (
                     hwndClient,         // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szChildClass,       // Client window class name
                     "Child No. 2",      // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndChildClient2) ;// Pointer to client window handle

      WinSendMsg (hwndChildFrame1, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

      WinSendMsg (hwndChildFrame2, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           /*-----------------------------------------------------
              Set reserved area of window to text string pointers
             -----------------------------------------------------*/

      WinSetWindowPtr (hwndChildClient1, QWL_USER, "I'm a child ...") ;
      WinSetWindowPtr (hwndChildClient2, QWL_USER, "... Me too!") ;

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      static CHAR szText [] = "I'm the parent of two children" ;
      HPS         hps ;
      RECTL       rcl ;

      switch (msg)
           {
           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGROUN
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }

 MRESULT EXPENTRY ChildWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
      {
      HPS   hps ;
      RECTL rcl ;

      switch (msg)
           {
           case WM_PAINT:
                hps = WinBeginPaint (hwnd, NULL, NULL) ;

                WinQueryWindowRect (hwnd, &rcl) ;

                WinDrawText (hps, -1, WinQueryWindowPtr (hwnd, QWL_USER), &rcl
                             CLR_NEUTRAL, CLR_BACKGROUND,
                             DT_CENTER | DT_VCENTER | DT_ERASERECT) ;

                WinEndPaint (hps) ;
                return 0 ;

           case WM_CLOSE:
                WinDestroyWindow (WinQueryWindow (hwnd, QW_PARENT, FALSE)) ;
                return 0 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }


 WELCOME4.C
 CD-ROM Disc Path:   \SAMPCODE\PROG_PM\CHAP03\WELCOME4.C

 /*-------------------------------------------------------------
    WELCOME4.C -- Creates a Top-Level Window and Three Children
   -------------------------------------------------------------*/

 #define INCL_WIN
 #include <os2.h>

 #define ID_BUTTON 1
 #define ID_SCROLL 2
 #define ID_ENTRY  3

 MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;

 int main (void)
      {
      static CHAR  szClientClass [] = "Welcome4" ;
      static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU   |
                                  FCF_BORDER        | FCF_MINBUTTON |
                                  FCF_SHELLPOSITION | FCF_TASKLIST ;
      HAB          hab ;
      HMQ          hmq ;
      HWND         hwndFrame,  hwndClient ;
      QMSG         qmsg ;
      RECTL        rcl ;

      hab = WinInitialize (0) ;
      hmq = WinCreateMsgQueue (hab, 0) ;

      WinRegisterClass (
                     hab,                // Anchor block handle
                     szClientClass,      // Name of class being registered
                     ClientWndProc,      // Window procedure for class
                     CS_SIZEREDRAW,      // Class style
                     0) ;                // Extra bytes to reserve

      hwndFrame = WinCreateStdWindow (
                     HWND_DESKTOP,       // Parent window handle
                     WS_VISIBLE,         // Style of frame window
                     &flFrameFlags,      // Pointer to control data
                     szClientClass,      // Client window class name
                     NULL,               // Title bar text
                     0L,                 // Style of client window
                     NULL,               // Module handle for resources
                     0,                  // ID of resources
                     &hwndClient) ;      // Pointer to client window handle

      WinSendMsg (hwndFrame, WM_SETICON,
                       WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
                       NULL) ;

           /*--------------------------------------------------------
              Find dimensions of client window for sizes of children
             --------------------------------------------------------*/

      WinQueryWindowRect (hwndClient, &rcl) ;
      rcl.xRight /= 3 ;                            // divide width in thirds

           /*---------------------------
              Create push button window
             ---------------------------*/

      WinCreateWindow (
                     hwndClient,                   // Parent window handle
                     WC_BUTTON,                    // Window class
                     "Big Button",                 // Window text
                     WS_VISIBLE                    // Window style
                          | BS_PUSHBUTTON,
                     10,                           // Window position
                     10,
                     (SHORT) rcl.xRight - 20,      // Window size
                     (SHORT) rcl.yTop - 20,
                     hwndClient,                   // Owner window handle
                     HWND_BOTTOM,                  // Placement window handle
                     ID_BUTTON,                    // Child window ID
                     NULL,                         // Control data
                     NULL) ;                       // Presentation parameters

           /*--------------------------
              Create scroll bar window
             --------------------------*/

      WinCreateWindow (
                     hwndClient,                   // Parent window handle
                     WC_SCROLLBAR,                 // Window class
                     NULL,                         // Window text
                     WS_VISIBLE                    // Window style
                          | SBS_VERT,
                     (SHORT) rcl.xRight + 10,      // Window position
                     10,
                     (SHORT) rcl.xRight - 20,      // Window size
                     (SHORT) rcl.yTop - 20,
                     hwndClient,                   // Owner window handle
                     HWND_BOTTOM,                  // Placement window handle
                     ID_SCROLL,                    // Child window ID
                     NULL,                         // Control data
                     NULL) ;                       // Presentation parameters

           /*---------------------------
              Create entry field window
             ---------------------------*/

      WinCreateWindow (
                     hwndClient,                   // Parent window handle
                     WC_ENTRYFIELD,                // Window class
                     NULL,                         // Window text
                     WS_VISIBLE                    // Window style
                          | ES_MARGIN
                          | ES_AUTOSCROLL,
                     2 * (SHORT) rcl.xRight + 10,  // Window position
                     10,
                     (SHORT) rcl.xRight - 20,      // Window size
                     (SHORT) rcl.yTop - 20,
                     hwndClient,                   // Owner window handle
                     HWND_BOTTOM,                  // Placement window handle
                     ID_ENTRY,                     // Child window ID
                     NULL,                         // Control data
                     NULL) ;                       // Presentation parameters

      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
           WinDispatchMsg (hab, &qmsg) ;

      WinDestroyWindow (hwndFrame) ;
      WinDestroyMsgQueue (hmq) ;
      WinTerminate (hab) ;
      return 0 ;
      }

 MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2
      {
      switch (msg)
           {
           case WM_COMMAND:
                switch (COMMANDMSG(&msg)->cmd)
                     {
                     case ID_BUTTON:
                          WinAlarm (HWND_DESKTOP, WA_NOTE) ;
                          return 0 ;
                     }
                break ;

           case WM_ERASEBACKGROUND:
                return 1 ;
           }
      return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
      }
