/******************************************************************************
*                                                                             *
*   Name:  STROKE.C                                                           *
*                                                                             *
*   Copyright : COPYRIGHT IBM CORPORATION, 1993                               *
*               LICENSED MATERIAL - PROGRAM PROPERTY OF IBM                   *
*                                                                             *
*   Description: This program processes all of the ink related functions      *
*                for the application.                                         *
*                                                                             *
*  DISCLAIMER OF WARRANTIES.  The following [enclosed] code is                *
*      sample code created by IBM Corporation. This sample code is not        *
*      part of any standard or IBM product and is provided to you solely      *
*      for  the purpose of assisting you in the development of your           *
*      applications.  The code is provided "AS IS", without                   *
*      warranty of any kind.  IBM shall not be liable for any damages         *
*      arising out of your use of the sample code, even if they have been     *
*      advised of the possibility of such damages.                            *
*                                                                             *
******************************************************************************/

#define  INCL_PM
#define  INCL_DOS

/******************************************************************************
* System include files                                                        *
******************************************************************************/
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <penpm.h>

/******************************************************************************
* Application specific include file                                           *
******************************************************************************/
#include "stroke.h"

/******************************************************************************
* Exported routines                                                           *
******************************************************************************/
HWND     APIENTRY InitTimedInk   ( HWND, MMTIME * );
BOOL     APIENTRY PlayTimedInk   ( HWND );
BOOL     APIENTRY StopTimedInk   ( HWND );
BOOL     APIENTRY RecordTimedInk ( HWND );
BOOL     APIENTRY PurgeTimedInk  ( HWND );
BOOL     APIENTRY DestroyTimeInk ( HWND );
MRESULT  EXPENTRY InkWinProc     ( HWND, ULONG, MRESULT, MRESULT );
BOOL     APIENTRY InkTimedInk    ( HWND, MMTIME );

/******************************************************************************
* Internal routines                                                           *
******************************************************************************/
ULONG             TimeToPoints    ( PMYWINDOWDATA, MMTIME );
BOOL              InkStroke       ( PMYWINDOWDATA, ULONG );
BOOL              AppendStrokes   ( PMYWINDOWDATA, PSTROKEDATA );
BOOL              ScaleStrokeData ( PMYWINDOWDATA );
BOOL              EraseTimedInk  ( PMYWINDOWDATA );

/******************************************************************************
* Used to track the current time reported from Multimedia.                    *
******************************************************************************/
MMTIME *pCurrMMTime;

/******************************************************************************
*                                                                             *
* Subroutine: InitTimedInk                                                    *
*                                                                             *
*   Function: This function performs the initialization for the inking        *
*             window. It allocates a block of memory for the data structure   *
*             used by the inking window. It then creates a transparent window *
*             to ink into.                                                    *
*                                                                             *
* Parameters: hwndParent - is the window handle to the parent window. The     *
*                          parent window will be the window containing the    *
*                          image to be annotated.                             *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
HWND APIENTRY InitTimedInk ( HWND hwndParent, MMTIME *pMMTime )
{
   PMYWINDOWDATA  pMyWindowData;
   APIRET         rc;
   HAB            hab;
   HMQ            hmq;
   QMSG           qmsg;
   HWND           hwndInking;
   static BOOL    WindowClassRegistered = FALSE;
   RECTL          rcl;

   pMyWindowData = (PMYWINDOWDATA) malloc ( sizeof ( MYWINDOWDATA ) );

   /***************************************************************************
   * Verify that the memory for the data structure has been allocated. If not *
   * return a NULLHANDLE to signify an error condition.                       *
   ***************************************************************************/
   if ( pMyWindowData == NULL )
   {
      return ( NULLHANDLE );
   };

   pCurrMMTime = pMMTime;

   pMyWindowData->hwndParent = hwndParent;
   pMyWindowData->hab = WinQueryAnchorBlock ( hwndParent );

   /***************************************************************************
   * Register the window class for the inking window, if it has not already   *
   * been done.                                                               *
   ***************************************************************************/
   if ( !WindowClassRegistered )
   {
      /************************************************************************
      * When the class is registered, reserve 4 more bytes to keep the        *
      * pointer to pMyWindowData structure.                                   *
      ************************************************************************/
      if ( !WinRegisterClass ( pMyWindowData->hab,
                               WC_INK_WINDOW,
                               InkWinProc,
                               CS_SYNCPAINT,
                               sizeof ( PMYWINDOWDATA ) ) )
      {
         /*********************************************************************
         * If the class has not been registered return a NULLHANDLE to        *
         * signify an error condition.                                        *
         *********************************************************************/
         free ( pMyWindowData );
         return ( NULLHANDLE );
      };

      /************************************************************************
      * If all goes well, set flag to indicate that the class has been        *
      * already been registered.                                              *
      ************************************************************************/
      WindowClassRegistered = TRUE;
   };

   /***************************************************************************
   * Determine the size of the window, then create then inking window.        *
   ***************************************************************************/
   WinQueryWindowRect ( pMyWindowData->hwndParent, &rcl );
   hwndInking = WinCreateWindow ( pMyWindowData->hwndParent,
                                  WC_INK_WINDOW,
                                  NULL,
                                  0,
                                  0,
                                  0,
                                  rcl.xRight,
                                  rcl.yTop,
                                  pMyWindowData->hwndParent,
                                  HWND_TOP,
                                  ID_INK_WINDOW,
                                  (PVOID) pMyWindowData,
                                  NULL );

   /***************************************************************************
   * Verify that the window has been created. If not return NULLHANDLE to     *
   * signify an error condition, else return the handle to the inking window. *
   ***************************************************************************/
   if ( !hwndInking )
   {
      free ( pMyWindowData );
      return ( NULLHANDLE );
   };
   return ( hwndInking );
}

/******************************************************************************
*                                                                             *
* Subroutine: DestroyTimedInk                                                 *
*                                                                             *
*   Function: This function performs the cleanup for the inking window and    *
*             the associated memory.                                          *
*                                                                             *
* Parameters: hwndParent - is the window handle to the parent window. The     *
*                          parent window will be the window containing the    *
*                          image to be annotated.                             *
*                                                                             *
* Internal                                                                    *
* References: EraseTimedInk - Cleans up display of window                     *
*             PurgeTimedInk - Purge previous recording                        *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY DestroyTimedInk ( HWND hWnd )
{
   PMYWINDOWDATA  pMyWindowData;
   HWND           hwndAudio;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   /*****************************************************************
   *  Close the audio dialog window along with this window.         *
   *****************************************************************/
   if (hwndAudio = WinWindowFromID(pMyWindowData->hwndParent,
                                   IDD_AUDIO))
   {
      WinSendMsg(hwndAudio,
                 WM_CLOSE,
                 (MPARAM) NULL,
                 (MPARAM) NULL);
   };

   EraseTimedInk ( pMyWindowData );
   PurgeTimedInk ( pMyWindowData->hwnd );
   WinDestroyWindow ( pMyWindowData->hwnd );

   free ( pMyWindowData );
   pMyWindowData = (PMYWINDOWDATA) NULL;
   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: ScaleStrokeData                                                 *
*                                                                             *
*   Function: Converts stroke data in high resolution to screen resolution.   *
*                                                                             *
* Parameters: hWnd - Window handle for the inking window.                     *
*                                                                             *
* Internal                                                                    *
* References: AppendStrokes - Appends new stroke to linked list pointer to by *
*                             pStrokeHead                                     *
*                                                                             *
* Pen For OS2                                                                 *
* References: WrtMapPointLong - Scales stroke data from one resolution to     *
*                               another.                                      *
*                                                                             *
******************************************************************************/
BOOL ScaleStrokeData ( PMYWINDOWDATA pMyWindowData )
{
   PSTROKEDATA    pTempStrokeHead;
   PSTROKEDATA    pTempCurrStroke;
   PSTROKEDATA    pStrokeScaled;
   RECTL          rcl;

   /***************************************************************************
   * If the high resolution stroke data hasn't been scaled yet, then allocate *
   * memory for the scaled stroke data.                                       *
   ***************************************************************************/
   if ( !pMyWindowData->pScaledHead )
   {

      /************************************************************************
      * Start off by saving the pointer to the high resolution stroke data,   *
      * setting the pointer to the current stroke to convert, and then        *
      * resetting the stroke head pointer to null.                            *
      ************************************************************************/
      pTempStrokeHead = pMyWindowData->pStrokeHead;
      pTempCurrStroke = pMyWindowData->pStrokeHead;
      pMyWindowData->pStrokeHead = NULL;

      /************************************************************************
      * For every stroke in the high resolution stroke data linked list,      *
      * allocate memory for the scaled stroke, copy high resolution stroke    *
      * information to the scaled stroke structure, allocate memory for the   *
      * scaled point information, and append new stroke to linked list.       *
      ************************************************************************/
      while ( pTempCurrStroke )
      {
         pStrokeScaled = (PSTROKEDATA) malloc (  sizeof(STROKEDATA) );
         memcpy ( pStrokeScaled,
                  pTempCurrStroke,
                  sizeof(STROKEDATA) );
         pStrokeScaled->pXY = (PPOINTL) malloc ( sizeof(POINTL) *
                                                 pStrokeScaled->ulNumPoints );
         AppendStrokes ( pMyWindowData, pStrokeScaled );
         pTempCurrStroke = pTempCurrStroke->psdFwd;
      };

      /************************************************************************
      * pStrokeHead now points to a linked list of stroke headers for the     *
      * scaled stroke data. Each stroke in this list has a pointer to an      *
      * array of POINTL data structures, however the points in the array have *
      * not yet been set. Save the pointer to the new linked list and restore *
      * the pointer to the high resolution linked list.                       *
      ************************************************************************/
      pMyWindowData->pScaledHead = pMyWindowData->pStrokeHead;
      pMyWindowData->pStrokeHead = pTempStrokeHead;
   };

   /***************************************************************************
   * For each stroke in the linked list, copy the high resolution point data, *
   * and then scale it to the appropriate size.                               *
   ***************************************************************************/
   WinQueryWindowRect ( pMyWindowData->hwnd, &rcl );
   pTempCurrStroke = pMyWindowData->pStrokeHead;
   pStrokeScaled = pMyWindowData->pScaledHead;
   while ( pTempCurrStroke )
   {
      memcpy ( pStrokeScaled->pXY,
               pTempCurrStroke->pXY,
               sizeof(POINTL) * pStrokeScaled->ulNumPoints );

      /************************************************************************
      * WrtMapPointLong will convert the input points to a new resolution.    *
      * The things to note here are that: 1) NULLHANDLE is used as the input  *
      * window handle; and 2) the calculation for the extents. The NULLHANDLE *
      * is used since we have already asked for the coordinates to be         *
      * adjusted relative to the window at liftoff time. The documentation    *
      * can be confusing on this point. If we were to provide the window      *
      * for this window, then all of the points would be offset by the        *
      * position of the window relative to the desktop origin. The extents    *
      * are adjusted for changes in the window size. There is an inverse      *
      * relationship between the extents and ink. Increase the extents and    *
      * ink will be shrink. Decrease the extents and the ink will expand.     *
      ************************************************************************/
      WrtMapPointLong(NULLHANDLE,
                      pStrokeScaled->pXY,
                      MP_SCALE,
                      ((pStrokeScaled->ulXExtent * pMyWindowData->OrgWindowSize.cx) / rcl.xRight),
                      ((pStrokeScaled->ulYExtent * pMyWindowData->OrgWindowSize.cy) / rcl.yTop  ),
                      0UL,
                      0UL,
                      pStrokeScaled->ulNumPoints);
      pTempCurrStroke = pTempCurrStroke->psdFwd;
      pStrokeScaled = pStrokeScaled->psdFwd;
   };
   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: PlayTimedInk                                                    *
*                                                                             *
*   Function: Initializes data structure for playback of a recording.         *
*                                                                             *
* Parameters: hWnd - Window handle for the inking window.                     *
*                                                                             *
* Internal                                                                    *
* References: EraseTimedInk - Cleanup display of window                       *
*             ScaleStrokeData - Scale data from high resolution               *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY PlayTimedInk ( HWND hWnd )
{
   PMYWINDOWDATA  pMyWindowData;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   EraseTimedInk ( pMyWindowData );

   /***************************************************************************
   * Convert high resolution stroke data to screen resolution                 *
   ***************************************************************************/
   if ( !pMyWindowData->pScaledHead )
   {
      ScaleStrokeData ( pMyWindowData );
   };

   /***************************************************************************
   * Prepare to playback the timed ink by setting current stroke to first     *
   * stroke in linked list. If the first stroke exists set pointer to current *
   * point and set the number of points remaining to be inked for the current *
   * stroke.                                                                  *
   ***************************************************************************/
   pMyWindowData->pCurrStroke = pMyWindowData->pScaledHead;
   if ( pMyWindowData->pCurrStroke != NULL )
   {
      pMyWindowData->NumPointsLeft  = pMyWindowData->pCurrStroke->ulNumPoints;
      pMyWindowData->pCurrPoint     = pMyWindowData->pCurrStroke->pXY;
   };

   /***************************************************************************
   * Set flags to indicate current state.                                     *
   ***************************************************************************/
   pMyWindowData->fPlay    = TRUE;
   pMyWindowData->fRecord  = FALSE;

   return ( IME_NO_ERROR );
}


/******************************************************************************
*                                                                             *
* Subroutine: RecordTimedInk                                                  *
*                                                                             *
*   Function: Initializes application in preparation to record timed ink.     *
*                                                                             *
* Parameters: hWnd - Window handle for inking window                          *
*                                                                             *
* Internal                                                                    *
* References: EraseTimedInk - Cleanup display of window                       *
*             PurgeTimedInk - Purge previous recording                        *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY RecordTimedInk ( HWND hWnd )
{
   PMYWINDOWDATA  pMyWindowData;
   APIRET         rc;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   /***************************************************************************
   * Cleanup the window for the next recording.                               *
   ***************************************************************************/
   EraseTimedInk ( pMyWindowData );

   /***************************************************************************
   * Free all of the old stroke data.                                         *
   ***************************************************************************/
   if ( pMyWindowData->pStrokeHead != NULL )
   {
      PurgeTimedInk ( hWnd );
   }

   /***************************************************************************
   * Set flags to indicate current state.                                     *
   ***************************************************************************/
   pMyWindowData->fPlay    = FALSE;
   pMyWindowData->fRecord  = TRUE;

   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: EraseTimedInk                                                   *
*                                                                             *
*   Function: Removes ink from the transparent inking window.                 *
*                                                                             *
* Parameters: pMyWindowData - Pointer to windon specific information          *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL EraseTimedInk ( PMYWINDOWDATA pMyWindowData )
{
   RECTL rcl;

   /***************************************************************************
   * Force the window to be repainted but without showing handwritten part.   *
   ***************************************************************************/
   GpiDeleteSegments ( pMyWindowData->hps, 1, pMyWindowData->NumSegments );
   pMyWindowData->NumSegments = 0;

   WinQueryWindowRect ( pMyWindowData->hwndParent, &rcl );
   WinInvalidateRect  ( pMyWindowData->hwndParent, &rcl, TRUE);

   /***************************************************************************
   * Set flags to indicate current state.                                     *
   ***************************************************************************/
   pMyWindowData->fPlay    = FALSE;
   pMyWindowData->fRecord  = FALSE;

   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: StopTimedInk                                                    *
*                                                                             *
*   Function: Stops recording of timed ink.                                   *
*                                                                             *
* Parameters: hWnd - Window handle for inking window                          *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY StopTimedInk ( HWND hWnd )
{
   RECTL          rcl;
   PMYWINDOWDATA  pMyWindowData;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   /***************************************************************************
   * Set flags to indicate current state.                                     *
   ***************************************************************************/
   pMyWindowData->fPlay    = FALSE;
   pMyWindowData->fRecord  = FALSE;

   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: PurgeTimedInk                                                   *
*                                                                             *
*   Function: Purges all strokes from memory.                                 *
*                                                                             *
* Parameters: hWnd - Window handle for inking window                          *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY PurgeTimedInk ( HWND hWnd )
{
   PMYWINDOWDATA  pMyWindowData;
   PSTROKEDATA    pTempStroke;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData           = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   /***************************************************************************
   * Set flags to indicate current state.                                     *
   ***************************************************************************/
   pMyWindowData->fPlay    = FALSE;
   pMyWindowData->fRecord  = FALSE;

   pMyWindowData->pCurrStroke = pMyWindowData->pStrokeHead;
   while ( pMyWindowData->pCurrStroke != NULL )
   {
      pTempStroke = pMyWindowData->pCurrStroke->psdFwd;

      free ( pMyWindowData->pCurrStroke->pXY );
      free ( pMyWindowData->pCurrStroke );

      pMyWindowData->pCurrStroke = pTempStroke;
   };
   pMyWindowData->pStrokeHead = NULL;

   pMyWindowData->pCurrStroke = pMyWindowData->pScaledHead;
   while ( pMyWindowData->pCurrStroke != NULL )
   {
      pTempStroke = pMyWindowData->pCurrStroke->psdFwd;

      free ( pMyWindowData->pCurrStroke->pXY );
      free ( pMyWindowData->pCurrStroke );

      pMyWindowData->pCurrStroke = pTempStroke;
   };
   pMyWindowData->pScaledHead = NULL;

   return ( IME_NO_ERROR );
}


/******************************************************************************
*                                                                             *
* Subroutine: AppendStrokes                                                   *
*                                                                             *
*   Function: Append a new stroke to the linked list of strokes for the       *
*             current recording.
*                                                                             *
* Parameters: pMyWindowData - Pointer to window instance data                 *
*             pStrokeData   - Pointer to the stroke data of the stroke to be  *
*                             appended to the linked list of strokes          *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL AppendStrokes ( PMYWINDOWDATA pMyWindowData, PSTROKEDATA pStrokeData )
{
   /***************************************************************************
   * If the stroke head pointer is null, then this is the first stroke in the *
   * linked list of strokes.                                                  *
   ***************************************************************************/
   if ( pMyWindowData->pStrokeHead == NULL )
   {
      pMyWindowData->pStrokeHead = pStrokeData;
      pMyWindowData->pCurrStroke = pStrokeData;
      return ( IME_NO_ERROR );
   };

   /***************************************************************************
   * Otherwise, append the new stroke to the end of the linked list.          *
   ***************************************************************************/
   pMyWindowData->pCurrStroke->psdFwd = pStrokeData;
   pStrokeData->psdBack = pMyWindowData->pCurrStroke;
   pMyWindowData->pCurrStroke = pStrokeData;
   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: InkTimedInk                                                     *
*                                                                             *
*   Function: This is a recursive routine that determines the strokes and     *
*             points that need to be inked for this time interval.            *
*                                                                             *
* Parameters: hWnd - Window handle to inking window                           *
*             ElapsedTime - This is the elapsed time ,in MMTIME units, from   *
*                           the start of the recording                        *
*                                                                             *
* Internal                                                                    *
* References: TimeToPoints - Convert a time duration to the number of points  *
*                            to ink for the time duration                     *
*             InkStroke    - Draw the stroke to the window                    *
*             InkTimedInk  - Determine how much more to ink                   *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL APIENTRY InkTimedInk ( HWND hWnd, MMTIME ElapsedTime )
{
   ULONG             PointsToInk;
   static   MMTIME   LastElapsedTime;
   PMYWINDOWDATA     pMyWindowData;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData  = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );

   /***************************************************************************
   * If the pointer to the window data is null return FALSE to indicate an    *
   * error condition.                                                         *
   ***************************************************************************/
   if ( pMyWindowData == NULL )
      return ( IME_INVALID_POINTER );

   /***************************************************************************
   * If the pointer to the current stroke is null then there are no more      *
   * strokes remaining to be inked, so return TRUE to indicate things are ok. *
   ***************************************************************************/
   if ( pMyWindowData->pCurrStroke == NULL )
   {
      return ( IME_NO_ERROR );
   };

   /***************************************************************************
   * ulUser1 contains the start time for the stroke. If the current elapsed   *
   * time is less than the start time for the current stroke,  then there are *
   * aren't any points to ink for this time period. Save the elapsed time for *
   * this call and TRUE to indicate things are ok.                            *
   ***************************************************************************/
   if ( ElapsedTime < pMyWindowData->pCurrStroke->ulUser1 )
   {
      LastElapsedTime = ElapsedTime;
      return ( IME_NO_ERROR );
   };

   /***************************************************************************
   * ulUser2 contains the end time for the stroke. If the current elapsed     *
   * time is greater than or equal to the end time for the current stroke,    *
   * then the rest of the current stroke needs to be inked and perhaps more.  *
   * Thus ink the rest of this stroke, set the last elapsed time, move to the *
   * next stroke and make the recursive call to determine if more points need *
   * to be inked, and return.                                                 *
   ***************************************************************************/
   if ( ElapsedTime >= pMyWindowData->pCurrStroke->ulUser2 )
   {
      InkStroke ( pMyWindowData, pMyWindowData->NumPointsLeft );
      LastElapsedTime = pMyWindowData->pCurrStroke->ulUser2;
      pMyWindowData->pCurrStroke = pMyWindowData->pCurrStroke->psdFwd;

      /************************************************************************
      * If there are more strokes in the linked list then move to next stroke *
      ************************************************************************/
      if ( pMyWindowData->pCurrStroke != NULL )
      {
         pMyWindowData->NumPointsLeft = pMyWindowData->pCurrStroke->ulNumPoints;
         pMyWindowData->pCurrPoint = pMyWindowData->pCurrStroke->pXY;
         InkTimedInk ( hWnd, ElapsedTime );
      };
      return ( IME_NO_ERROR );
   };

   /***************************************************************************
   * If we get to this point then only a portion of the current stroke needs  *
   * to be inked at this time. Determine how many points to ink, ink the      *
   * points, set the elapsed time, and return.                                *
   ***************************************************************************/
   PointsToInk = TimeToPoints ( pMyWindowData, ElapsedTime - LastElapsedTime );
   InkStroke ( pMyWindowData, PointsToInk );
   LastElapsedTime = ElapsedTime;
   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: TimeToPoints                                                    *
*                                                                             *
*   Function: This function calcualates and returns the number of points to   *
*             be inked for a given amount of time.                            *
*                                                                             *
* Parameters: pMyWindowData - Pointer to window data                          *
*             DeltaTime     - The amount of time that needs to be inked       *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
ULONG TimeToPoints ( PMYWINDOWDATA pMyWindowData, MMTIME DeltaTime )
{
   ULONG NumOfPoints;

   /***************************************************************************
   * MMTIME is 1/3000 of a second. The sample rate is given in points per     *
   * second. Thus the number of points to ink is calculated by:               *
   *                                                                          *
   *           1 SECOND                                NUM OF POINTS          *
   *        ---------------- X DeltaTime MMTIMEUNITS X -------------          *
   *        3000 MMTIMEUNITS                           SECOND                 *
   *                                                                          *
   * Once the estimate of the number of points has been made, check to see if *
   * it exceeds the number of points remaining to be inked in the current     *
   * stroke. Return the minimum of these two numbers.                         *
   ***************************************************************************/
   NumOfPoints = DeltaTime * pMyWindowData->pCurrStroke->ulSampleRate / 3000 ;
   if ( pMyWindowData->NumPointsLeft < NumOfPoints )
   {
      NumOfPoints = pMyWindowData->NumPointsLeft;
   };
   return ( NumOfPoints );
}


/******************************************************************************
*                                                                             *
* Subroutine: InkStroke                                                       *
*                                                                             *
*   Function: This function draws the ink to the display during playback. It  *
*             uses paths to draw 2 pel wide geometric lines. It then updates  *
*             the number of points still waiting to be drawn, and moves the   *
*             current point pointer to the next point to be drawn.            *
*                                                                             *
* Parameters: pMyWindowData - Pointer to window data                          *
*             NumOfPoints   - Number of points to ink from the current stroke *
*                                                                             *
* Internal                                                                    *
* References: None                                                            *
*                                                                             *
* Pen For OS2                                                                 *
* References: None                                                            *
*                                                                             *
******************************************************************************/
BOOL InkStroke ( PMYWINDOWDATA pMyWindowData, ULONG NumOfPoints )
{
   /***************************************************************************
   * If the current point is the first point in the stroke then the stroke    *
   * not been inked yet. So move to the first point in the presentation space *
   * in preparation for the polyline.                                         *
   ***************************************************************************/
   if ( pMyWindowData->pCurrPoint == pMyWindowData->pCurrStroke->pXY )
   {
      GpiOpenSegment ( pMyWindowData->hps, ++pMyWindowData->NumSegments );
      GpiMove ( pMyWindowData->hps, pMyWindowData->pCurrPoint );
   };

   /***************************************************************************
   * Now draw the stroke on the screen                                        *
   ***************************************************************************/
   GpiBeginPath ( pMyWindowData->hps, 1 );
   GpiPolyLine ( pMyWindowData->hps, NumOfPoints, pMyWindowData->pCurrPoint );
   GpiEndPath ( pMyWindowData->hps );
   GpiStrokePath ( pMyWindowData->hps, 1, 0 );

   /***************************************************************************
   * Update the window data to indicate the number of points yet to be inked  *
   * in the current stroke and move the current point pointer to the next     *
   * next point to the last point inked.                                      *
   ***************************************************************************/
   pMyWindowData->NumPointsLeft -= NumOfPoints;
   pMyWindowData->pCurrPoint    += NumOfPoints;

   /***************************************************************************
   * If last point in stroke has been inked, then close the graphic segment.  *
   ***************************************************************************/
   if ( pMyWindowData->pCurrPoint == NULL )
   {
      GpiCloseSegment ( pMyWindowData->hps );
   };

   return ( IME_NO_ERROR );
}

/******************************************************************************
*                                                                             *
* Subroutine: InkWinProc                                                      *
*                                                                             *
*   Function: Window procedure for inking window. The inking window is a      *
*             transparent window that we will be inking to during the capture *
*             and playback of stroke data.                                    *
*                                                                             *
* Parameters: hWnd - Window handle                                            *
*             msg  - Message Id                                               *
*             mp1  - Message Parameter 1                                      *
*             mp2  - Message Parameter 2                                      *
*                                                                             *
* Internal                                                                    *
* References: AppendStrokes   - Append strokes to linked list                 *
*             ScaleStrokeData - Scale strokes from high resolution            *
*             PlayTimedInk    - Prepare to playback recording                 *
*             InkTimedInk     - Draw ink to window                            *
*             StopTimedInk    - Stop playback or recording                    *
*                                                                             *
* Pen For OS2                                                                 *
* References: WrtQueryEventData - Get the event data from Pen for OS/2        *
*             WrtQueryStrokeData - Get stroke data                            *
*                                                                             *
******************************************************************************/
MRESULT EXPENTRY InkWinProc ( HWND     hWnd,
                              ULONG    msg,
                              MRESULT  mp1,
                              MRESULT  mp2 )
{
   SIZEL          PageSize;
   PMYWINDOWDATA  pMyWindowData;
   ULONG          buflen;
   PSTROKEDATA    pStrokeData;
   static MMTIME  TouchdownTime = 0;
   static MMTIME  LiftoffTime   = 0;
   APIRET         rc;
   static USHORT  PointCounter = 0;
   static POINTL  pPoint[8];
   static BOOL    StrokeBufferOverrun = FALSE;
   RECTL          rcl;
   HPS            hpspaint;
   WRTEVENTDATA   EventData;

   /***************************************************************************
   * Get pointer to the window data.                                          *
   ***************************************************************************/
   pMyWindowData = (PMYWINDOWDATA) WinQueryWindowPtr ( hWnd, 0 );
   switch ( msg )
   {
      /************************************************************************
      * Create this transparent window and setup the pMyWindowData. This      *
      * structure is kept in reserved area.                                   *
      ************************************************************************/
      case  WM_CREATE:
      {
         /*********************************************************************
         * Initialize window instance data                                    *
         *********************************************************************/
         pMyWindowData = (PMYWINDOWDATA) PVOIDFROMMP ( mp1 );
         if ( !WinSetWindowPtr ( hWnd, 0, (PVOID) pMyWindowData ) )
            return ( (MPARAM) TRUE );

         pMyWindowData->hwnd = hWnd;

         /*********************************************************************
         * Create the presentation space for the window                       *
         *********************************************************************/
         pMyWindowData->hab = WinQueryAnchorBlock ( hWnd );
         pMyWindowData->hdc = WinOpenWindowDC ( hWnd );
         DevQueryCaps ( pMyWindowData->hdc, CAPS_WIDTH, 1L, &PageSize.cx );
         DevQueryCaps ( pMyWindowData->hdc, CAPS_HEIGHT, 1L, &PageSize.cy );
         pMyWindowData->hps = GpiCreatePS ( pMyWindowData->hab,
                                            pMyWindowData->hdc,
                                            &PageSize,
                                            ( PU_PELS      |
                                              GPIT_NORMAL  |
                                              GPIF_LONG    |
                                              GPIA_ASSOC   ) );
         if ( pMyWindowData->hps == GPI_ERROR )
         {
            return ( (MPARAM) TRUE );
         };

         /*********************************************************************
         * Prepare the presentation space for drawing and retaining           *
         * the graphic primatives                                             *
         *********************************************************************/
         GpiSetDrawingMode         ( pMyWindowData->hps, DM_DRAWANDRETAIN );
         GpiSetDrawControl         ( pMyWindowData->hps,
                                     DCTL_ERASE,
                                     DCTL_OFF );
         pMyWindowData->NumSegments = 0;

         /*********************************************************************
         * Set initial presentation space attributes                          *
         *********************************************************************/
         pMyWindowData->CurrentColor = CLR_RED;
         GpiSetColor ( pMyWindowData->hps,
                       pMyWindowData->CurrentColor );
         GpiSetLineWidthGeom ( pMyWindowData->hps, 2L );
         GpiSetLineEnd ( pMyWindowData->hps, LINEEND_ROUND );
         GpiSetLineJoin ( pMyWindowData->hps, LINEJOIN_ROUND );

         /*********************************************************************
         * Set flags to initial state                                         *
         *********************************************************************/
         pMyWindowData->td      = FALSE;
         pMyWindowData->fPlay   = FALSE;
         pMyWindowData->fRecord = FALSE;

         /*********************************************************************
         * Make the window visible and return                                 *
         *********************************************************************/
         WinShowWindow ( hWnd, TRUE );
         return ( (MRESULT) FALSE );
         break;
      }

      /************************************************************************
      * Get the start time of stroke, Capture mouse messages, move to         *
      * the point where the pen touches the workpad to ink.                   *
      ************************************************************************/
      case WM_TOUCHDOWN:
      {
         /*********************************************************************
         * Only process this message if we are in the process of creating     *
         * a new recording.                                                   *
         *********************************************************************/
         if ( pMyWindowData->fRecord )
         {
            /******************************************************************
            * Get the current time in MMTIME                                  *
            ******************************************************************/
            TouchdownTime = *(pCurrMMTime);
            GpiOpenSegment  ( pMyWindowData->hps, ++pMyWindowData->NumSegments );
            WinQueryWindowRect ( hWnd, &rcl );
            pMyWindowData->OrgWindowSize.cx = rcl.xRight;
            pMyWindowData->OrgWindowSize.cy = rcl.yTop;

            PointCounter = 0;

            pMyWindowData->td = TRUE;
            WinSetCapture ( HWND_DESKTOP, hWnd );

            /******************************************************************
            * Note that the (SHORT) cast is required in order to maintain     *
            * sign.                                                           *
            ******************************************************************/
            pMyWindowData->points.x = (SHORT) SHORT1FROMMP ( mp1 );
            pMyWindowData->points.y = (SHORT) SHORT2FROMMP ( mp1 );

            GpiMove ( pMyWindowData->hps, &pMyWindowData->points );

            /******************************************************************
            * TDN_INFINTE: Inform Pen for OS/2 that the mouse button down     *
            *              is not required.                                   *
            * TDN_HIFREQ_MOUSEMOVE: Inform Pen for OS/2 to report the points  *
            *              as mousemoves but at the sensor sample rate.       *
            * TDN_NO_INK_STROKE: Inform Pen for OS/2 not to ink the points    *
            *              since we will be doing our inking.                 *
            ******************************************************************/
            return ( (MRESULT) ( TDN_INFINITE         |
                                 TDN_HIFREQ_MOUSEMOVE |
                                 TDN_NO_INK_STROKE ));
         };
         break;
      }

      /************************************************************************
      *  For this message, the inking is done to show the stroke.             *
      ************************************************************************/
      case WM_MOUSEMOVE:
      {
         if ( pMyWindowData != NULL )
         {
            if ( pMyWindowData->td && pMyWindowData->fRecord )
            {
               /***************************************************************
               * This release of Pen for OS/2 has a maximum limit to the size *
               * of the stroke buffer. It is limited to 1024 points or eight  *
               * seconds per stroke. If the maximum size of the stroke buffer *
               * is reached, then inform the user via a tone.                 *
               ***************************************************************/
               if ( StrokeBufferOverrun )
               {
                  DosBeep ( 4000, 16 );
                  return ( (MRESULT) TRUE );
               };

               /***************************************************************
               * Get the event data to see if the stroke buffer has been      *
               * been filled.                                                 *
               ***************************************************************/
               EventData.cbStructSize = sizeof(EventData);
               WrtQueryEventData ( &EventData );
               if ( EventData.flEventStatus & WRT_BUFFER_OVERRUN )
               {
                  DosBeep ( 4000, 16 );

                  /************************************************************
                  * Force the inking for all of the remaining points.         *
                  ************************************************************/
                  GpiBeginPath ( pMyWindowData->hps, 1 );
                  GpiPolyLine ( pMyWindowData->hps, PointCounter, pPoint );
                  GpiEndPath ( pMyWindowData->hps );
                  GpiStrokePath ( pMyWindowData->hps, 1, 0 );
                  GpiCloseSegment ( pMyWindowData->hps );
                  StrokeBufferOverrun = TRUE;
                  return ( (MRESULT) TRUE );
               };

               /***************************************************************
               * Keep the (x,y) coordinate in a buffer of 8 entries.          *
               * Note that the (SHORT) cast is required in order to maintain  *
               * sign.                                                        *
               ***************************************************************/
               pPoint[PointCounter].x = (SHORT) SHORT1FROMMP( mp1 );
               pPoint[PointCounter].y = (SHORT) SHORT2FROMMP( mp1 );
               PointCounter++;
               if (PointCounter == 8)
               {
                  GpiBeginPath ( pMyWindowData->hps, 1 );
                  GpiPolyLine ( pMyWindowData->hps, 8L, pPoint );
                  GpiEndPath ( pMyWindowData->hps );
                  GpiStrokePath ( pMyWindowData->hps, 1, 0 );
                  PointCounter = 0;
               }
               return ( (MRESULT) TRUE );
            };
         };
         break;
      }

      /************************************************************************
      *  Stroke ends. Collect stroke end time, No more mouse message          *
      *  capture, Get the stroke data and Call AppendStrokes to adjust        *
      *  the stroke linked list in pMyWindowData.                             *
      ************************************************************************/
      case WM_LIFTOFF:
      {
         if ( pMyWindowData->td && pMyWindowData->fRecord )
         {
            /******************************************************************
            * Get the current time in MMTIME                                  *
            ******************************************************************/
            LiftoffTime = *(pCurrMMTime);
            WinSetCapture ( HWND_DESKTOP, NULLHANDLE );
            pMyWindowData->td = FALSE;

            /******************************************************************
            * If the buffer was overrun, then the points have already been    *
            * displayed.                                                      *
            ******************************************************************/
            if ( !StrokeBufferOverrun )
            {
               /******************************************************************
               * Force the inking for all of the remaining points.               *
               * Note that the (SHORT) cast is required in order to maintain     *
               * sign.                                                           *
               ******************************************************************/
               pPoint[PointCounter].x = (SHORT) SHORT1FROMMP( mp1 );
               pPoint[PointCounter].y = (SHORT) SHORT2FROMMP( mp1 );
               PointCounter++;
               GpiBeginPath ( pMyWindowData->hps, 1 );
               GpiPolyLine ( pMyWindowData->hps, PointCounter, pPoint );
               GpiEndPath ( pMyWindowData->hps );
               GpiStrokePath ( pMyWindowData->hps, 1, 0 );
               GpiCloseSegment ( pMyWindowData->hps );
            };

            /******************************************************************
            * Get the size of the stroke buffer.                              *
            ******************************************************************/
            buflen = 0;
            pStrokeData = NULL;
            rc = WrtQueryStrokeData ( (PBYTE) pStrokeData, &buflen, hWnd, QSD_SCALE, 0, 0, 0 );
            if ( rc )
              return (MRESULT) TRUE;

            if ( !buflen )
              return (MRESULT) TRUE;

            /******************************************************************
            * Allocate memory for the stroke buffer.                          *
            ******************************************************************/
            pStrokeData = (PSTROKEDATA) malloc ( buflen );
            if ( !pStrokeData )
            {
               return ( (MRESULT) TRUE );
            };

            /******************************************************************
            * Get the stroke data                                             *
            ******************************************************************/
            pStrokeData->cbStructSize = sizeof ( STROKEDATA );
            if ( WrtQueryStrokeData ( (PBYTE) pStrokeData,
                                      &buflen,
                                      pMyWindowData->hwnd,
                                      QSD_STANDARDIZED_RES,
                                      0,
                                      0,
                                      0 ) )
               return ( (MRESULT) LO_STROKE_PROCESSED );

            /******************************************************************
            * Append current stroke to linked list                            *
            ******************************************************************/
            AppendStrokes ( pMyWindowData, pStrokeData );

            /******************************************************************
            * Use existing structure entries for the stroke to hold the       *
            * touchdown and liftoff times for the current stroke              *
            ******************************************************************/
            pStrokeData->ulUser1 = TouchdownTime;
            pStrokeData->ulUser2 = LiftoffTime;

            StrokeBufferOverrun = FALSE;

            return ( (MRESULT) LO_STROKE_PROCESSED );

         }
         else
         {
            return ( (MRESULT) LO_DEFAULT );
         };
         return ( (MRESULT) FALSE );
      }

      /************************************************************************
      * Repaint the window, let the default window procedure handle.          *
      ************************************************************************/
      case  WM_PAINT:
      {
         hpspaint = WinBeginPaint  ( hWnd, pMyWindowData->hps, &rcl );
         GpiDrawChain ( hpspaint );
         WinEndPaint  ( hpspaint );
         return ( (MRESULT) TRUE );
      }

      /************************************************************************
      * Clean up before we close the window                                   *
      ************************************************************************/
      case WM_DESTROY:
      {
         GpiDestroyPS ( pMyWindowData->hps );
         return ( (MRESULT) FALSE );
      }

      /************************************************************************
      * When the parent window is resized, this window is resized. Scale the  *
      * data to the new size, and playback the ink into the window at the new *
      * scale.                                                                *
      ************************************************************************/
      case WM_SIZE:
      {
         if ( pMyWindowData != NULL )
         {
            if ( pMyWindowData->pStrokeHead )
            {
               WinShowWindow ( pMyWindowData->hwnd, FALSE );
               ScaleStrokeData ( pMyWindowData );
               PlayTimedInk ( pMyWindowData->hwnd );
               InkTimedInk ( pMyWindowData->hwnd, *(pCurrMMTime) );
               StopTimedInk ( pMyWindowData->hwnd );
               WinShowWindow ( pMyWindowData->hwnd, TRUE );
            }
            else
               EraseTimedInk ( pMyWindowData );
         };
         return ( (MRESULT) FALSE );
      }

      default:
         break;
   };

   return( (MRESULT) WinDefWindowProc ( hWnd, msg, mp1, mp2 )  );
};
