/**************************************************************************
 *
 * SOURCE FILE NAME = TDIFIXED.C
 *
 * DESCRIPTIVE NAME = Touch dd, 1st code seg
 *
 * Copyright : COPYRIGHT IBM CORPORATION, 1991, 1992
 *             LICENSED MATERIAL - PROGRAM PROPERTY OF IBM
 *             REFER TO COPYRIGHT INSTRUCTION FORM#G120-2083
 *             RESTRICTED MATERIALS OF IBM
 *             IBM CONFIDENTIAL
 *
 * VERSION = V2.0
 *
 * DATE        07/30/91
 *
 * DESCRIPTION
 *
 *
 * FUNCTIONS      StratEntry,          IdcEntry,         IdcDisableSupport,
 *                IdcProcessPkt,       ReportProtEvent,  ReportVdmEvent,
 *                QueueWrite,          RemapEvent,       MapCoord,
 *                flush,               vtUpdateOTData,   vtUpdOneTouch,
 *                vtFindStablePoint,   vtCheckSPData,    vtUpdSPBuffer,
 *                vtUpdStablePoint.
 *
 * ENTRY POINTS:
 *
 * DEPENDENCIES:
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
 * CHANGE ACTIVITY =
 *  DATE      FLAG        APAR   CHANGE DESCRIPTION
 *  --------  ----------  -----  --------------------------------------
 *  mm/dd/yy  @Vr.mpppxx  xxxxx  xxxxxxx
 ****************************************************************************/

/*
** Includes
*/
#define  INCL_OS2STD
#define  INCL_DOSINFOSEG
#define  INCL_DOSSESMGR
#define  INCL_DOSMISC
#include <os2.h>
#include <stdlib.h>
#include "toudefs.h"
#include "reqblk.h"
#include "toutypes.h"
#include "devhlp.h"
#include "touasm.h"
#include "tddcalls.h"

/*
** Defines
**   Define to convert vdm code to pdd code
*/
#define  SPBUFFERMAX          TDI_SEL_SMAX
#define  SM_FLAG_SENT         1
#define  SM_FLAG_DETECTED     2
#define  SM_PUSH_HARDER       0
#define  SM_LIFT_OFF          1
#define  SM_ONETOUCH          2
#define  SM_STABLE_POINT      4
#define  PRIVENTRY            PASCAL NEAR

/*
** Typedefs
**   Prototypes
*/

FDDENTRY StratEntry(USHORT);
FDDENTRY IdcEntry(USHORT);
STATIC VOID NEAR PASCAL IdcDisableSupport(VOID);
STATIC VOID NEAR PASCAL IdcProcessPkt(VOID);
STATIC VOID NEAR PASCAL ReportProtEvent(VOID);
STATIC VOID NEAR PASCAL ReportVdmEvent(VOID);
VOID   FAR PASCAL QueueWrite(PSGCB,PEVENT);
STATIC VOID NEAR PASCAL RemapEvent(PEVENT,PEVENT,PSGCB,struct _DELTA *);
STATIC USHORT NEAR PASCAL MapCoord(USHORT,USHORT,USHORT,USHORT,USHORT,PLONG);
STATIC VOID FAR flush(PSGCB);
extern VOID FAR PASCAL NewStratEntry(PREQBLK);
extern VOID FAR ReqInit(PREQBLK);
VOID PRIVENTRY vtUpdStablePoint(PSGCB pSgcb,PEVENT pEv);
VOID PRIVENTRY vtUpdSPBuffer(PSGCB pSgcb,USHORT usX,USHORT usY);
VOID PRIVENTRY vtCheckSPData(PSGCB pSgcb,PEVENT pEv);
BOOL PRIVENTRY vtFindStablePoint(PSGCB pSgcb,PEVENT pEv);
VOID PRIVENTRY vtUpdOneTouch(PSGCB pSgcb,PEVENT pEv);
VOID PRIVENTRY vtUpdateOTData(PSGCB pSgcb,PEVENT pEv);

/*
** External global data
**   Data declared near as inherit DS from strategy entry & module is compiled
**   with /A?w switch (do not reload DS on entry)
*/
extern VOID(FAR PASCAL *NEAR DevHlp_Entry)();
extern UCHAR NEAR fDeInstall;
extern USHORT NEAR InitStage;
extern PGINFOSEG NEAR pGlobalInfoSeg;
extern UCHAR NEAR NumSess;
extern USHORT NEAR fVdmEnabled;
extern PSGCB NEAR pFgndSgcb;
extern MONREC NEAR TouEvent;
extern MONREC NEAR LastEvent;
extern INTPKT NEAR IntPkt;
extern USHORT NEAR FgndSess;
extern USHORT NEAR MaxEQLen;
extern UCHAR NEAR fExclAccess;

/*
** Local global data
*/
STATIC USHORT NEAR IntNestLvl = 0;
STATIC USHORT NEAR XRange;
STATIC USHORT NEAR YRange;
STATIC USHORT NEAR ZRange;

STATIC struct _DELTA
{
  LONG X;
  LONG Y;
} NEAR Delta;

STATIC USHORT fFlush = 1;             /* init to force event reporting       */


/****************************************************************************
 *
 * FUNCTION NAME = StratEntry
 *
 * DESCRIPTION   =
 *   This must be in this segment as its address is only near offset in
 *   dev hdr.  As regs are accessed by touasm macros this function must
 *   be cdecl.
 *
 *   Called from : Kernel (via device header)
 *
 *
 * INPUT         = (USHORT reg)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE - via request block
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

extern FDDENTRY StratEntry(USHORT reg)
{
  PREQBLK pReqBlk;

  pReqBlk = (PREQBLK)MAKEP(_ES(reg), _BX(reg));

  if (SELECTOROF(DevHlp_Entry) != 0 && !fDeInstall)
  {                               /* init already done && not de-installed   */
    NewStratEntry(pReqBlk);
  }
  else
  {                                   /* needs init                          */
    if (pReqBlk->cmd == REQF_INIT)
    {                                 /* do init                             */
      ReqInit(pReqBlk);
    }
    else
      if (fDeInstall)
      {                               /* deinstalled                         */
        pReqBlk->status = REQE_UCMD;
      }
    pReqBlk->status |= REQS_DONE;     /* set done                            */
  }
}                                     /* StratEntry                          */


/****************************************************************************
 *
 * FUNCTION NAME = IdcEntry
 *
 * DESCRIPTION   =
 *   Called from : DDDD IDC
 *
 *
 * INPUT         = (USHORT reg)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = Via registers (CY,AX)
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

extern FDDENTRY IdcEntry(USHORT reg)
{
  USHORT ret_val = 0;

  if (InitStage != -1)
  {                                   /* idc not disabled                    */
    switch (_AX(reg))
    {
      case 1 :
        /* Do not need to get int_pkt address*/
        /* as it is in our data seg*/
        IdcProcessPkt();
        clc();                        /* no error                            */
        break;

      case 2 :
        IdcDisableSupport();
        clc();                        /* no error                            */
        break;

      default  :
        stc();                        /* error                               */
        ret_val = 1;
    }                                 /* switch                              */
  }
  else
  {                                   /* idc disabled                        */
    clc();                   /* no error ??? shouldnt this be an error ???   */
    ret_val = 1;
  }

  /* NB. Make sure carry flag not corrupted in 'C' end proc protocols        */
  _asm
  {
        mov     ax, ret_val
  }

}                                     /* IdcEntry                            */


/****************************************************************************
 *
 * FUNCTION NAME = IdcDisableSupport
 *
 * DESCRIPTION   =
 *   Called from : IdcEntry (seg2)
 *
 *
 * INPUT         = (VOID)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID NEAR PASCAL IdcDisableSupport(VOID)
{
  InitStage = -1;                     /* disable idc support                 */
}                                     /* IdcDisableSupport                   */


/****************************************************************************
 *
 * FUNCTION NAME = IdcProcessPkt
 *
 * DESCRIPTION   =
 *    Context:     Interrupt
 *
 *
 * INPUT         = (VOID)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID NEAR PASCAL IdcProcessPkt(VOID)
{
  ++IntNestLvl;
  TouEvent.Event.Time = pGlobalInfoSeg->time;

  /* NB *** all refences to psgcb should be pfgndsgcb at interrupt time ***  */
  if (FgndSess < NumSess && !(FgndSess == SESN_SHELL && fExclAccess))
  {                     /* Full Screen session AND not PM exclusive access   */
    if (pFgndSgcb->Hdle_Count > 0)
      ReportProtEvent();
  }
  else
  {                                  /* VDM session OR PM exclusive access   */
    if (fVdmEnabled)
      ReportVdmEvent();
  }
  --IntNestLvl;
}                                     /* IdcProcessPkt                       */


/****************************************************************************
 *
 * FUNCTION NAME = ReportProtEvent
 *
 * DESCRIPTION   =
 *   Called from : ProtectEvent (seg2)
 *                 AbsProtectEvent (seg2)
 *
 *
 * INPUT         = (VOID)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID NEAR PASCAL ReportProtEvent(VOID)
{
  USHORT tdX,tdY;

  /* Copy interrupt packet to local data area*/
  TouEvent.Event.Status = IntPkt.Status;
  TouEvent.Event.X = IntPkt.X;
  TouEvent.Event.Y = IntPkt.Y;
  TouEvent.Event.Z = IntPkt.Z;

  /* save range of reported data for remappong routine*/
  XRange = IntPkt.RX;
  YRange = IntPkt.RY;
  ZRange = IntPkt.RZ;

  /* Remap event status, x, y, z & implement selection mechanisms*/
  RemapEvent(&(TouEvent.Event), &(LastEvent.Event), pFgndSgcb, &Delta);

  if (TouEvent.Event.Status & pFgndSgcb->EventMask)
  {                                   /* some event to report                */
    if (!(tdX = (Delta.X >=  (LONG)pFgndSgcb->CoordSys.XHys + 1000l
              || Delta.X <= -(LONG)pFgndSgcb->CoordSys.XHys)) && !fFlush)
    {                                 /* not moved far enough, keep old X    */
      TouEvent.Event.X = LastEvent.Event.X;
    }

    if (!(tdY = (Delta.Y >=  (LONG)pFgndSgcb->CoordSys.YHys + 1000l
              || Delta.Y <= -(LONG)pFgndSgcb->CoordSys.YHys)) && !fFlush)
    {                                 /* not moved far enough, keep old Y    */
      TouEvent.Event.Y = LastEvent.Event.Y;
    }

    if (TouEvent.Event.Status != LastEvent.Event.Status ||
        TouEvent.Event.Z != LastEvent.Event.Z || tdX || tdY || fFlush)
    {                  /* Event different from last or first after a flush   */
      fFlush = 0;                     /* reset flag                          */
      LastEvent = TouEvent;

      if (pFgndSgcb->Chain_Size == 0)
      {                               /* no monitor chains active            */
        if (!pFgndSgcb->DevStatus.eq_busy)
        {                             /* not busy                            */
          /*
          ** ??? are ints enabled here, if yes, then need the disable
          ** around q write
          */
          _asm                        /* save int state & disable            */
          {
              pushf
              cli
          }

          QueueWrite(pFgndSgcb, &TouEvent.Event);

          _asm                        /* resore int. state                   */
          {
              popf
          }
        }
      }                               /* no monitors active                  */
      else
      {                               /* some monitor chains active          */
        DHMonWrite( &TouEvent,
                    pFgndSgcb->Chain_Hdle,
                    sizeof(TouEvent),
                    (UCHAR)1          /* no wait                             */
                  );
      }                               /* if - monitors active                */
    }                                 /* if - event different                */
  }                                   /* if - some event to report           */
}                                     /* ReportProtEvent                     */


/****************************************************************************
 *
 * FUNCTION NAME = ReportVdmEvent
 *
 * DESCRIPTION   =
 *   Called from : VdmEvent (seg2)
 *                 AbsVdmEvent (seg2)
 *
 *
 * INPUT         = (VOID)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID NEAR PASCAL ReportVdmEvent(VOID)
{
  /* Copy interrupt packet to local data area                                */
  TouEvent.Event.Status = IntPkt.Status;
  TouEvent.Event.X = IntPkt.X;
  TouEvent.Event.Y = IntPkt.Y;
  TouEvent.Event.Z = IntPkt.Z;

  if (TouEvent.Event.Status != LastEvent.Event.Status ||
      TouEvent.Event.X != LastEvent.Event.X ||
      TouEvent.Event.Y != LastEvent.Event.Y ||
      TouEvent.Event.Z != LastEvent.Event.Z)
  {                                   /* event different from last           */
    LastEvent = TouEvent;             /* save for compare, next time         */
    TouEvent.MFlags = FgndSess;       /* supply sess id for vdm              */
    TouVddCall(1l, &TouEvent, 0l);
  }
}                                     /* ReportVdmEvent                      */


/****************************************************************************
 *
 * FUNCTION NAME = QueueWrite
 *
 * DESCRIPTION   =
 *   Called from : ReportProtEvent (seg2)
 *                 MonitorHandler (seg3)
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEvent)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID FAR PASCAL QueueWrite(PSGCB pSgcb, PEVENT pEvent)
{

  register _segment seg = (_segment)pSgcb;
  register SGCB _based(seg) * pCb =
             (SGCB _based(seg) *)OFFSETOF(pSgcb);

  /* USHORT          QueueSize = pCb->QueueSize;                             */
  *pCb->Eq_Tail++ = *pEvent;

  /* If queue full, overwrite oldest element                                 */
#ifndef  SETQ
  if (pCb->Eq_Size >= MaxEQLen)
#else
  if (pCb->Eq_Size >= pCb->QueueSize)
#endif
    ++pCb->Eq_Head;
  else
    ++pCb->Eq_Size;

  /* Check for ptr wrap*/
#ifndef  SETQ
  if (pCb->Eq_Head >= pCb->Eq_Start+MaxEQLen)
#else
  if (pCb->Eq_Head >= pCb->Eq_Start+pCb->QueueSize)
#endif
  {                                   /* wrap head ptr                       */
    pCb->Eq_Head = pCb->Eq_Start;
  }

#ifndef  SETQ
  if (pCb->Eq_Tail >= pCb->Eq_Start+MaxEQLen)
#else
  if (pCb->Eq_Tail >= pCb->Eq_Start+pCb->QueueSize)
#endif
  {                                   /* wrap tail ptr                       */
    pCb->Eq_Tail = pCb->Eq_Start;
  }

  /* Run any blocked threads waiting for an event                            */
  if (pCb->DevStatus.blocked)
  {                                   /* is blocked                          */
    /* DHRun( MAKEP( SELECTOROF(pSgcb), pCb->Eq_Start) );                    */
    DHRun((PEVENT)pCb->Eq_Start);
  }
}                                     /* QueueWrite                          */


/****************************************************************************
 *
 * FUNCTION NAME = RemapEvent
 *
 * DESCRIPTION   =
 *
 *
 * INPUT         = (PEVENT pEv,PEVENT pLast,PSGCB pCb,struct _DELTA *pDelta)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID NEAR PASCAL RemapEvent(PEVENT pEv, PEVENT pLast, PSGCB pCb,
                                   struct _DELTA *pDelta)
{
  REG USHORT OldStatus;

  pEv->X = MapCoord(pCb->CoordSys.XMin, pCb->CoordSys.XMax, XRange, pEv->X,
                    pLast->X, &(pDelta->X));
  pEv->Y = MapCoord(pCb->CoordSys.YMin, pCb->CoordSys.YMax, YRange, pEv->Y,
                    pLast->Y, &(pDelta->Y));
  pEv->Z = MapCoord(pCb->CoordSys.ZMin, pCb->CoordSys.ZMax, ZRange, pEv->Z,
                    pLast->Z, NULL);

  /* Remap status */
  OldStatus = pEv->Status;            /* get status to be remaped            */
  pEv->Status = 0;                    /* init new status                     */

  /*
  **  ??? shouldn't select turned on/off be faked in queue read routine
  **      so as to ensure it gets set, rather than being lost in q
  **      touch-down/lift-off can also be lost
  */
  if ((OldStatus & 3) == 1)
    pEv->Status |= TDI_MASK_LIFT_OFF;

  if ((OldStatus & 3) == 2)
    pEv->Status |= TDI_MASK_TOUCH_DOWN;

  if ((OldStatus & 3) == 3)
    pEv->Status |= TDI_MASK_ON_SCREEN;

  if (OldStatus & 0x8000)
    pEv->Status |= TDI_MASK_DATA_ERROR;

  /* Selection mechanisms! */
  switch (pCb->SelMech.Type)
  {
    case  SM_PUSH_HARDER :
      if (OldStatus & 4)
        pEv->Status |= TDI_MASK_SELECT_ON;
      break;

    case  SM_LIFT_OFF :
      if ((OldStatus & 3) == 1)
        pEv->Status |= TDI_MASK_SELECT_ON;
      break;

    case  SM_ONETOUCH|SM_PUSH_HARDER :
    case  SM_ONETOUCH|SM_LIFT_OFF :
      vtUpdOneTouch(pCb, pEv);
      break;

    case  SM_STABLE_POINT|SM_PUSH_HARDER :
    case  SM_STABLE_POINT|SM_LIFT_OFF :
      vtUpdStablePoint(pCb, pEv);
      break;
  }                                   /* switch                              */

  /* create select detect deltas */
  if (pEv->Status & TDI_MASK_SELECT_ON)
  {                                   /* selection on                        */
    if (!(pLast->Status & TDI_MASK_SELECT_ON))

      /* just gone on */
      pEv->Status |= TDI_MASK_SELECT_TURNED_ON;
  }
  else
  {                                   /* selection off                       */
    if ((pLast->Status & TDI_MASK_SELECT_ON))

      /* just gone off*/
      pEv->Status |= TDI_MASK_SELECT_TURNED_OFF;
  }
}                                     /* RemapEvent                          */


/****************************************************************************
 *
 * FUNCTION NAME = MapCoord
 *
 * DESCRIPTION   = Map to new coord system with movement from last position
 *
 *
 * INPUT         = (USHORT Min,USHORT Max,USHORT Range,USHORT Val,
 *                  USHORT Old,PLONG plDelta)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC USHORT NEAR PASCAL MapCoord(USHORT Min, USHORT Max, USHORT Range,
                                   USHORT Val, USHORT Old, PLONG plDelta)
{
  REG SHORT scale;
  REG SHORT new;
  LONG big;
  LONG range;
  LONG delta;

  /* Scale factor corrected for swapped coord systems*/
  if ((scale = Max-Min) < 0)
    scale -= 1;
  else
    scale += 1;

  big = (LONG)Val * (LONG)scale;      /* scale up ...                        */
  range = (LONG)Range + 1;            /* ... adjust range ...                */
  new = (USHORT)(big / range);   /* ... back to new system (no offset, yet)  */

  if (plDelta != NULL)
  {                              /* Delta used for hysteresis calculations   */
    /*
    ** Calculate real movement * 1000 from last position & not
    **   quantised to new coord system. Eg. Half way between coords
    **   3 & 4 would return a delta of 1500.
    **
    ** NB. Add in 'Min' to 'new' as 'Old' was returned from this
    **   routine previously with 'Min' added.
    ** Calculate delta from: course delta + remainder
    */
    delta = 1000l * (LONG)((SHORT)(new + Min - Old)) +
            (1000l * (big - (LONG)new * range)) / range;

    /* Return delta corrected for swapped coord systems*/
    *plDelta = (scale < 0) ? -delta : delta;
  }                                   /* if -                                */

  return (new+Min);              /* return new value with coord offset added */
}                                     /* MapCoord                            */


/****************************************************************************
 *
 * FUNCTION NAME = flush
 *
 * DESCRIPTION   =
 *
 *
 * INPUT         = (PSGCB pSgcb)
 *                  pSgcb -> VDM data.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

STATIC VOID FAR flush(PSGCB pSgcb)
{
  /* Assumes disables/enables done outside this routine                      */
  pSgcb->Eq_Size = 0;
  pSgcb->Eq_Head = pSgcb->Eq_Tail = pSgcb->Eq_Start;

  fFlush = 1;                         /* force event reporting               */
}                                     /* flush                               */


/****************************************************************************
 *
 * FUNCTION NAME = vtUpdateOTData
 *
 * DESCRIPTION   =
 *   PSEUDO-CODE -
 *      if data not already sent (only one select between TD and LO)
 *         AND X and Y within tolerance {
 *          save averaged X, Y
 *          if method is not lift-off AND count packets were within tolerance..
 *              Set the detected flag.
 *      } else {
 *          reset counter
 *          save this X, Y as new start for averaging
 *      }
 *      Update stray SingleTouch elements, Z and Status.
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEv)
 *                  pSgcb -> VDM data.
 *                  pEv   -> Event packet.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID PRIVENTRY vtUpdateOTData(PSGCB pSgcb, PEVENT pEv)
{
  if (!(pSgcb->smData.usFlag & SM_FLAG_SENT) &&
       (USHORT)(abs(pEv->X-pSgcb->xyzSingleTouch.x)) <=
         pSgcb->SelMech.Tolerance &&
       (USHORT)(abs(pEv->Y-pSgcb->xyzSingleTouch.y)) <=
         pSgcb->SelMech.Tolerance)
  {
    pSgcb->xyzSingleTouch.x += pEv->X;
    pSgcb->xyzSingleTouch.x /= 2;
    pSgcb->xyzSingleTouch.y += pEv->Y;
    pSgcb->xyzSingleTouch.y /= 2;

    if (!(pSgcb->SelMech.Type & SM_LIFT_OFF) && !(--pSgcb->smData.usOTCount))
      pSgcb->smData.usFlag |= SM_FLAG_DETECTED;
  }
  else
  {
    pSgcb->smData.usOTCount = pSgcb->SelMech.NumPoints;
    pSgcb->xyzSingleTouch.x = pEv->X;
    pSgcb->xyzSingleTouch.y = pEv->Y;
  }

  pSgcb->xyzSingleTouch.z = pEv->Z;
  pSgcb->xyzSingleTouch.Status = pEv->Status;
}


/****************************************************************************
 *
 * FUNCTION NAME = vtUpdOneTouch
 *
 * DESCRIPTION   = Update selection bit using One-Touch algorithm
 *   PSEUDO-CODE -
 *      Note: Only one event between touch-down and lift-off
 *            will be sent with the selection bit set.
 *      Ignore off-screen events.
 *      If event is a touch-down, save away X, Y data, clear flags, counters.
 *      If event is lift-off and method is by lift-off
 *          Update single-touch structure.
 *          Force event to be sent with selection bit set.
 *      If event is on-screen
 *          Update single-touch structure.
 *      If selection criteria have been met (SM_FLAG_DETECTED)
 *         AND event hasn't already been sent with selection bit set {
 *              Update current event with SingleTouch X, Y
 *              Set selection bit.
 *              Flag that an event has been sent.
 *      }
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEv)
 *                  pSgcb -> VDM data.
 *                  pEv   -> Event packet.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID PRIVENTRY vtUpdOneTouch(PSGCB pSgcb, PEVENT pEv)
{

  if (pEv->Status & TDI_MASK_LIFT_OFF)
  {                          /* if this is a lift-off possibly return data   */
    if (pSgcb->SelMech.Type & SM_LIFT_OFF)
    {
      vtUpdateOTData(pSgcb, pEv);

      /* force it to be sent */
      pSgcb->smData.usFlag |= SM_FLAG_DETECTED;
    }
  }
  else
    if (pEv->Status & TDI_MASK_TOUCH_DOWN)
    {                                 /* set up data for first point         */
      /* ??? vtUpdateXYZData(&pSgcb->xyzSingleTouch, pEv);                   */
      pSgcb->smData.usFlag = 0;
      pSgcb->smData.usOTCount = pSgcb->SelMech.NumPoints;
    }
    else
      if (pEv->Status & TDI_MASK_ON_SCREEN)
      {
        vtUpdateOTData(pSgcb, pEv);
      }

  if (!(pSgcb->smData.usFlag & SM_FLAG_SENT) &&
       (pSgcb->smData.usFlag & SM_FLAG_DETECTED))
  {
    pEv->X = pSgcb->xyzSingleTouch.x;
    pEv->Y = pSgcb->xyzSingleTouch.y;
    pEv->Status |= TDI_MASK_SELECT_ON;
    pSgcb->smData.usFlag |= SM_FLAG_SENT;
  }
}


#define  NEXTSPPTR(p)     ((USHORT)((p>=(SPBUFFERMAX-1)) ? (0) : (p+1)))
#define  PREVSPPTR(p)     ((USHORT)((p) ? (p-1) : (SPBUFFERMAX-1)))

/****************************************************************************
 *
 * FUNCTION NAME = vtFindStablePoint
 *
 * DESCRIPTION   = Search buffer for stable point.
 *   PSEUDO-CODE -
 *      Note:   Buffer should already contain at least one co-ord
 *              pair before reaching here as vtUpdSPBuffer() is
 *              called before vtCheckSPData() which got us this far.
 *
 *      Set buffer 'pointers' to most recent data.
 *      Use incoming event X, Y as reference.
 *      Begin searching buffer *backwards* for stable point.
 *      Search starts at current buffer position (SPBufferHead has
 *        already been adjusted) and continues until 'Limit' points
 *        have been processed, the buffer is exhausted, or a stable
 *        region is found.
 *      Update StablePoint structure if found.
 *      Return whether found or not.
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEv)
 *                  pSgcb -> VDM data.
 *                  pEv   -> Event packet.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE  - Stable region found
 *
 * RETURN-ERROR  = FALSE - not found
 *
 ****************************************************************************/

BOOL PRIVENTRY vtFindStablePoint(PSGCB pSgcb, PEVENT pEv)
{
  BOOL   rc = FALSE;
  USHORT SPBuffHead, SPBuffTail;
  ULONG  ulX = 0, ulY = 0;
  USHORT i = 0, x, y, count;

  SPBuffHead = PREVSPPTR(pSgcb->smData.usSPBuffHead);
  SPBuffTail = pSgcb->smData.usSPBuffTail;

  if (SPBuffHead == SPBuffTail)
    SPBuffTail = PREVSPPTR(SPBuffTail);

  pSgcb->xyzStablePoint.x = pEv->X;   /* 1st reference point                 */
  pSgcb->xyzStablePoint.y = pEv->Y;
  count = pSgcb->SelMech.NumPoints;

  while ((i++ < pSgcb->SelMech.SearchLimit) && (SPBuffHead != SPBuffTail))
  {
    x = pSgcb->smData.SPBuffer[SPBuffHead].x;
    y = pSgcb->smData.SPBuffer[SPBuffHead].y;

    if ((USHORT)(abs(x-pSgcb->xyzStablePoint.x)) <= pSgcb->SelMech.Tolerance &&
        (USHORT)(abs(y-pSgcb->xyzStablePoint.y)) <= pSgcb->SelMech.Tolerance)
    {
      if (!--count)
      {
        count = pSgcb->SelMech.NumPoints;
        for (i = 0; i < count; i++)
        {
          ulX += pSgcb->smData.SPBuffer[SPBuffHead].x;
          ulY += pSgcb->smData.SPBuffer[SPBuffHead].y;
          SPBuffHead = NEXTSPPTR(SPBuffHead);
        }
        pSgcb->xyzStablePoint.x = (USHORT)(ulX/count);
        pSgcb->xyzStablePoint.y = (USHORT)(ulY/count);
        rc = TRUE;
        break;
      }
    }
    else
    {
      count = pSgcb->SelMech.NumPoints;
      pSgcb->xyzStablePoint.x = x;
      pSgcb->xyzStablePoint.y = y;
    }
    SPBuffHead = PREVSPPTR(SPBuffHead);
  }                                   /* while                               */
  return (rc);
}


/****************************************************************************
 *
 * FUNCTION NAME = vtCheckSPData
 *
 * DESCRIPTION   = Update current event if stable point found.
 *   PSEUDO-CODE -
 *      Update current event (pEv) with new data
 *        if stable region found in buffer.
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEv)
 *                  pSgcb -> VDM data.
 *                  pEv   -> Event packet.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID PRIVENTRY vtCheckSPData(PSGCB pSgcb, PEVENT pEv)
{
  if (!(pSgcb->smData.usFlag & SM_FLAG_SENT))
  {
    if (vtFindStablePoint(pSgcb, pEv))
    {
      pEv->X = pSgcb->xyzStablePoint.x;
      pEv->Y = pSgcb->xyzStablePoint.y;
      pEv->Status |= TDI_MASK_SELECT_ON;
      pSgcb->smData.usFlag |= SM_FLAG_SENT;
    }
  }
}


/****************************************************************************
 *
 * FUNCTION NAME = vtUpdSPBuffer
 *
 * DESCRIPTION   = Add X, Y co-ordinate pair to Stable Point buffer
 *   PSEUDO-CODE -
 *      Add the incoming X, Y co-ordinates to stable point buffer
 *      at the position indicated by SPBufferHead.
 *      Advance buffer pointers.
 *
 *
 * INPUT         = (PSGCB pSgcb,USHORT usX,USHORT usY)
 *                  pSgcb -> VDM data
 *                  usX   -  X incoming co-ordinate
 *                  usY   -  Y incoming co-ordinate
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID PRIVENTRY vtUpdSPBuffer(PSGCB pSgcb, USHORT usX, USHORT usY)
{
  USHORT SPBuffHead, SPBuffTail;

  SPBuffHead = pSgcb->smData.usSPBuffHead;
  SPBuffTail = pSgcb->smData.usSPBuffTail;
  pSgcb->smData.SPBuffer[SPBuffHead].x = usX;
  pSgcb->smData.SPBuffer[SPBuffHead].y = usY;

  if (++SPBuffHead >= SPBUFFERMAX)
    SPBuffHead = 0;
  if (SPBuffTail == SPBuffHead)
  {                                   /* overrun, advance tail               */
    if (++SPBuffTail >= SPBUFFERMAX)
      SPBuffTail = 0;
  }
  pSgcb->smData.usSPBuffHead = SPBuffHead;
  pSgcb->smData.usSPBuffTail = SPBuffTail;
}


/****************************************************************************
 *
 * FUNCTION NAME = vtUpdStablePoint
 *
 * DESCRIPTION   = Update selection bit using Stable Point algorithm
 *   PSEUDO-CODE -
 *      if event is off-screen, ignore.
 *      if event is touch-down, reset flags, flush queue.
 *      if event is lift-off and method is lift-off
 *          Add event data to stable point buffer head (X and Y)
 *          Look for stable point.
 *      if event is on-screen {
 *          Add event data to stable point buffer head (X and Y)
 *          if selection method is not lift-off and Z > push threshold
 *              Look for stable point.
 *      }
 *
 *
 * INPUT         = (PSGCB pSgcb,PEVENT pEv)
 *                  pSgcb -> VDM data.
 *                  pEv   -> Event packet.
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

VOID PRIVENTRY vtUpdStablePoint(PSGCB pSgcb, PEVENT pEv)
{

  if (pEv->Status & TDI_MASK_TOUCH_DOWN)
  {
    pSgcb->smData.usFlag = 0;         /* reset flag                          */
    pSgcb->smData.usSPBuffHead = 0;   /* clear queue                         */
    pSgcb->smData.usSPBuffTail = 0;   /*                                     */
  }
  else
    if (pEv->Status & TDI_MASK_ON_SCREEN)
    {                                 /* add event to buffer                 */
    #if 0
      vtUpdSPBuffer(pSgcb, pEv->X, pEv->Y);
      if (!(pSgcb->SelMech.Type&SM_LIFT_OFF) &&
           (pEv->Z > pSgcb->thData.usPushSelection))
        vtCheckSPData(pSgcb, pEv);
    #endif
    }
    else
      if (pEv->Status & TDI_MASK_LIFT_OFF)
      {
        if (pSgcb->SelMech.Type & SM_LIFT_OFF)
        {
          vtUpdSPBuffer(pSgcb, pEv->X, pEv->Y);
          vtCheckSPData(pSgcb, pEv);
        }
      }
}

/*
** end
*/
