//****************************************************************************
//
//  File:       joycal.c
//  Content:    Joystick calibration dialog
//  History:
//   Date	By	Reason
//   ====	==	======
//   11-dec-94	craige	split out of joycpl.c; some tweaks
//   15-dec-94	craige	allow N joysticks
//   17-dec-94	craige	new UI as requested by ChrisB
//   18-dec-94	craige	process UV
//   05-jan-95	craige	new centering confirmation messages
//   04-mar-95	craige	bug 10761 - separate strings for pluralization
//			bug 12036 - now works when "Back" clicked off of
//				    custom 4-axis with POV hat
//
//  Copyright (c) Microsoft Corporation 1994-1995
//
//****************************************************************************

#pragma pack (8)

#include "stdafx.h"
#include "joycpl.h"
#include "resource.h"
#include "pov.h"
#include "assert.h"

#include "baseids.h"

#include "comstr.h"

#undef	NewLoadString
#define NewLoadString(a, b, c, d)     \
	pszCommonString->LoadString(b); \
	lstrcpy( c, (LPCTSTR)*pszCommonString )

// Context sensitive help stuff!
static void OnContextMenu(WPARAM wParam);
static void OnHelp       (LPARAM);

static const DWORD CalibrateHelpIDs[] =
{
    IDC_JOYLIST1,  IDC_JOYLIST1,
    IDC_JOYLIST2,  IDC_JOYLIST2,
    IDC_JOYLIST3,  IDC_JOYLIST3,
    IDC_JOYLIST4,  IDC_JOYLIST4,
    0,  0
};



/*
 * This has the look and feel of a wizard, but isn't   This leads to the
 * obvious...
 *
 * Q: Why isn't this a "real" wizard?
 *
 * A: - it doesn't have multiple pages, it has a single page.  the user
 *	sees different joystick items activate and de-activate on the dialog
 *	as he/she calibrates each axis. fussing with multiple sheets for each
 *	axis would be confusing and unnecessary.
 */

/*
 * calibration states
 */
// %%% debug %%% alpha days
extern "C"
{
    typedef enum
    {
        JCS_INIT=-1,
        JCS_XY_CENTER1,
        JCS_XY_MOVE,
        JCS_XY_CENTER2,
        JCS_Z_MOVE,
        JCS_Z_PLACEHOLDER,
        JCS_R_MOVE,
        JCS_R_PLACEHOLDER,
        JCS_U_MOVE,
        JCS_U_PLACEHOLDER,
        JCS_V_MOVE,
        JCS_V_PLACEHOLDER,
        JCS_POV_MOVEUP,
        JCS_POV_MOVERIGHT,
        JCS_POV_MOVEDOWN,
        JCS_POV_MOVELEFT,
        JCS_FINI
    } cal_states;

    typedef enum
    {
        JC_XY=0,
        JC_Z,
        JC_POV_UP,
        JC_POV_RIGHT,
        JC_POV_DOWN,
        JC_POV_LEFT,
        JC_R,
        JC_U,
        JC_V,
        JC_FINI
    } cal_wins;

// variables used in calibration
    typedef struct
    {
        LPGLOBALVARS    pgv;
        cal_states      cState;
        BOOL        bHasTimer;
        BOOL        bUseTimer;
        HINSTANCE       hinst;
        JOYINFOEX       ji;
        JOYRANGE        jr;
        DWORD       pov[JOY_POV_NUMDIRS];
        int         iAxisCount;
        BOOL        bPOVdone;
    } CALVARS, *LPCALVARS;

    extern "C" WINMMAPI MMRESULT WINAPI joyConfigChanged(DWORD);


#define JOY_CALIB_FLAGS	JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | \
			JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | \
			JOY_RETURNBUTTONS | JOY_RETURNRAWDATA

/*
 * we use raw data during calibration; RAW_SHIFT allows us to convert to
 * a reasonable "real" value
 */
#define RAW_SHIFT	100
} // extern "C"

// setDefaultButton - make a button the default window
static void setDefaultButton( HWND hwnd, HWND hwdb )
{
    // Optimized New Method
    SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hwdb, (LPARAM)TRUE);

/* Optimized Old Method
   DWORD	style;
   HWND	hCtrl;
   int	idList[] = { IDC_JOYCALDONE, IDC_JOYCALNEXT,
                      IDC_JOYCALBACK, IDC_JOYPICKPOV };
   #define SIZEOF_LIST 4

    // turn off the current default push button
   for (short i=0; i < SIZEOF_LIST; i++ ) 
    {
        hCtrl = GetDlgItem( hwnd, idList[i] );

      if (hCtrl)
      {
        style = GetWindowLong( hCtrl, GWL_STYLE );

        if ( style & BS_DEFPUSHBUTTON ) 
           {
               style &= ~BS_DEFPUSHBUTTON;
           style |= BS_PUSHBUTTON;
           SetWindowLong( hCtrl, GWL_STYLE, style );
            break;
           }
      }
    }

   // make the specified button the default
   style = GetWindowLong( hwdb, GWL_STYLE );
   style &= ~(BS_PUSHBUTTON|BS_DEFPUSHBUTTON);
   style |= BS_DEFPUSHBUTTON;
   SetWindowLong( hwdb, GWL_STYLE, style );
*/

} // setDefaultButton 

/*
 * setLabel
 *
 * set the label for an axis based on current calibration state
 */
static void setLabel(
                    LPGLOBALVARS pgv,
                    HWND hwnd,
                    UINT id,
                    LPJOYREGHWCONFIG pcfg,
                    DWORD bit  )
{
    char        str[MAX_STR];
    int         type;
    HINSTANCE   hinst;
    HWND        hwtext;

    hinst = GetResourceInstance( );
    assert(hinst);

    // get text for this axis label...
    if( pcfg->dwUsageSettings & JOY_US_ISOEM )
    {
        type = pcfg->dwType - JOY_HW_LASTENTRY;
        if( type < 0 || type >= pgv->pjd->oemCount )
        {
            type = -1;
        }
    } else
    {
        type = -1;
    }

    switch( id )
    {
    case IDC_JOYLIST1_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].xy_label[0] == 0) )
        {
            NewLoadString( hinst, IDS_XYAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].xy_label );
        break;

    case IDC_JOYLIST2_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].z_label[0] == 0 ) )
        {
            NewLoadString( hinst, IDS_ZAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].z_label );
        break;

    case IDC_JOYLIST3_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].r_label[0] == 0) )
        {
            NewLoadString( hinst, IDS_RAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].r_label );
        break;

    case IDC_JOYLIST4_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].u_label[0] == 0) )
        {
            NewLoadString( hinst, IDS_UAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].u_label );
        break;

    case IDC_JOYLIST5_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].v_label[0] == 0) )
        {
            NewLoadString( hinst, IDS_VAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].v_label );
        break;

    case IDC_JOYPOV_LABEL:
        if( (type == -1) || (pgv->pjd->oemList[type].pov_label[0] == 0) )
        {
            NewLoadString( hinst, IDS_POVAXIS_LABEL, str, sizeof( str ) );
            if( !lstrlen(str) ) return;
        } else lstrcpy( str, pgv->pjd->oemList[type].pov_label );
        break;
    }

    hwtext = GetDlgItem( hwnd, id );
    ASSERT (::IsWindow(hwtext));

    if( hwtext != NULL )
        SetWindowText( hwtext, str );

} /* setLabel */

// enableCalWindows - enable or disable specific calibration windows
static void enableCalWindows(
                            LPGLOBALVARS pgv,
                            LPJOYREGHWCONFIG pcfg,
                            HWND hwnd,
                            cal_wins id )
{
    BOOL        on;
    HWND        hwlb;
    HWND        hwb;
    int         iid;

    // set up the buttons
    hwb = GetDlgItem( hwnd,IDC_JOYCALDONE );
    ASSERT (hwb);

    if( id == JC_FINI )
    {
        hwlb = GetDlgItem( hwnd, IDC_JOYCALNEXT );
        ASSERT (::IsWindow(hwlb));
        ShowWindow(hwlb, SW_HIDE );

        EnableWindow( hwb, TRUE );
        ShowWindow( hwb, SW_NORMAL );
        SetFocus( hwb );
        setDefaultButton( hwnd, hwb );
    } else
    {
        hwlb = GetDlgItem( hwnd, IDC_JOYCALNEXT );
        ASSERT (::IsWindow(hwlb));
        ShowWindow( hwlb, SW_NORMAL );

        EnableWindow( hwb, FALSE );
        ShowWindow( hwb, SW_HIDE );
    }

    setLabel( pgv, hwnd, IDC_JOYLIST1_LABEL, pcfg, JOY_ISCAL_XY );

    // set up the XY window
    on = FALSE;

    if( id == JC_XY )
        on = TRUE;

    hwlb = GetDlgItem( hwnd, IDC_JOYLIST1 );
    ASSERT (::IsWindow(hwlb));

    if( !on )
        InvalidateRect( hwlb, NULL, TRUE );

    EnableWindow( hwlb, on );
    EnableWindow( GetDlgItem( hwnd, IDC_JOYLIST1_LABEL ), on );

    /*
     * set up the Z window
     */
    on = FALSE;
    if( id == JC_Z )
        on = TRUE;

    hwlb = GetDlgItem( hwnd, IDC_JOYLIST2 );
    if( !on )
    {
        InvalidateRect( hwlb, NULL, TRUE );
    }
    EnableWindow( hwlb, on );
    EnableWindow( GetDlgItem( hwnd, IDC_JOYLIST2_LABEL ), on );

    /*
     * set up the R window
     */
    on = FALSE;
    if( id == JC_R )
    {
        on = TRUE;
    }
    hwlb = GetDlgItem( hwnd, IDC_JOYLIST3 );
    if( !on )
    {
        InvalidateRect( hwlb, NULL, TRUE );
    }
    EnableWindow( hwlb, on );
    EnableWindow( GetDlgItem( hwnd, IDC_JOYLIST3_LABEL ), on );

    /*
     * set up the U window
     */
    on = FALSE;
    if( id == JC_U )
    {
        on = TRUE;
    }
    hwlb = GetDlgItem( hwnd, IDC_JOYLIST4 );
    if( hwlb != NULL )
    {
        if( !on )
        {
            InvalidateRect( hwlb, NULL, TRUE );
        }
        EnableWindow( hwlb, on );
        EnableWindow( GetDlgItem( hwnd, IDC_JOYLIST4_LABEL ), on );
    }

    /*
     * set up the V window
     */
    on = FALSE;
    if( id == JC_V )
    {
        on = TRUE;
    }
    hwlb = GetDlgItem( hwnd, IDC_JOYLIST5 );
    if( hwlb != NULL )
    {
        if( !on )
        {
            InvalidateRect( hwlb, NULL, TRUE );
        }
        EnableWindow( hwlb, on );
        EnableWindow( GetDlgItem( hwnd, IDC_JOYLIST5_LABEL ), on );
    }

    /*
     * set up the POV icon
     */
    on = FALSE;
    if( id >= JC_POV_UP && id <= JC_POV_LEFT )
    {
        on = TRUE;
    }
    EnableWindow( GetDlgItem( hwnd, IDC_JOYPOV_LABEL ), on );
    hwb = GetDlgItem( hwnd, IDC_JOYPICKPOV );
    EnableWindow( hwb, on );
    if( on )
    {
        ShowWindow( hwb, SW_NORMAL );
        SetFocus( hwb );
        setDefaultButton( hwnd, hwb );
        switch( id )
        {
        case JC_POV_UP:
            iid = IDI_JOYPOV_UP;
            break;
        case JC_POV_RIGHT:
            iid = IDI_JOYPOV_RIGHT;
            break;
        case JC_POV_LEFT:
            iid = IDI_JOYPOV_LEFT;
            break;
        case JC_POV_DOWN:
            iid = IDI_JOYPOV_DOWN;
            break;
        }
    } else
    {
        ShowWindow( hwb, SW_HIDE );
        UpdateWindow( hwb );
        iid = IDI_JOYPOV_GRAYED;
    }
    ChangeIcon( hwnd, iid, IDC_JOYPOV );

} /* enableCalWindows */


/*
 * getJoyName - get the name of a joystick
 */
static int getJoyName( LPJOYREGHWCONFIG pcfg, BOOL plural )
{
    int str2id;

    if( pcfg->hws.dwFlags & JOY_HWS_ISYOKE )
    {
        str2id = ( plural ) ? IDS_JOYCAL_YOKES : IDS_JOYCAL_YOKE;
    } else if( pcfg->hws.dwFlags & JOY_HWS_ISCARCTRL )
    {
        str2id = ( plural ) ? IDS_JOYCAL_CARS : IDS_JOYCAL_CAR;
    } else if( pcfg->hws.dwFlags & JOY_HWS_ISGAMEPAD )
    {
        str2id = ( plural ) ? IDS_JOYCAL_GAMEPADS : IDS_JOYCAL_GAMEPAD;
    } else
    {
        str2id = ( plural ) ? IDS_JOY2S : IDS_JOY2;
    }

    return(str2id);

} /* getJoyName */

/*
 * joyCalStateChange - calibration state change
 */
static BOOL joyCalStateChange( LPCALVARS pcv, HWND hwnd, BOOL back )
{
    HINSTANCE       hinst;
    HWND        hwtext;
    int         strid;
    int         stridx = 0;     // BUG FIX: CML 6/21/96 (FLASH RAID 270)
    int         str2id;
    int         str3id;
    int         str4id;
    char        str[2*MAX_STR];
    char        buff[2*MAX_STR];
    char        str2[64];
    char        str3[64];
    char        str4[64];
    BOOL        done;
    LPJOYREGHWCONFIG    pcfg;
    BOOL        rc;
    int         type;
    LPGLOBALVARS    pgv;
    BOOL        isdone;

    assert(pcv);
    assert(hwnd);

    /*
     * move to the next state: get the appropriate string
     * to display, and enable the correct controls
     */
    pgv = pcv->pgv;
    assert(pgv);
    rc = TRUE;
    done = FALSE;
    pcfg = &pgv->joyHWCurr;
    str2id = -1;
    str3id = -1;
    str4id = -1;
    (pcv->cState) = (cal_states) (pcv->cState + ((cal_states) 1));
    EnableWindow( GetDlgItem( hwnd, IDC_JOYCALBACK ), back );

    while( !done )
    {
        done = TRUE;

        switch( pcv->cState )
        {
        case JCS_XY_CENTER1:
            /*
             * init. range variables
             */
            pcv->jr.jpMin.dwX = (DWORD) -1;
            pcv->jr.jpMin.dwY = (DWORD) -1;
            pcv->jr.jpMin.dwZ = (DWORD) -1;
            pcv->jr.jpMin.dwR = (DWORD) -1;
            pcv->jr.jpMin.dwU = (DWORD) -1;
            pcv->jr.jpMin.dwV = (DWORD) -1;
            pcv->jr.jpMax.dwX = 0;
            pcv->jr.jpMax.dwY = 0;
            pcv->jr.jpMax.dwZ = 0;
            pcv->jr.jpMax.dwR = 0;
            pcv->jr.jpMax.dwU = 0;
            pcv->jr.jpMax.dwV = 0;

            // set strings to display
            stridx = CALSTR1;

            if( pcfg->hws.dwFlags & JOY_HWS_ISYOKE )
            {
                strid = IDS_JOYCALXY_CENTERYOKE;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISCARCTRL )
            {
                strid = IDS_JOYCALXY_CENTERCAR;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISGAMEPAD )
            {
                strid = IDS_JOYCALXY_CENTERGAMEPAD;
            } else
            {
                strid = IDS_JOYCALXY_CENTER;
            }

            enableCalWindows( pgv, pcfg, hwnd, JC_XY );
            break;

        case JCS_XY_MOVE:
            stridx = CALSTR2;
            if( pcfg->hws.dwFlags & JOY_HWS_ISYOKE )
            {
                strid = IDS_JOYCALXY_MOVEYOKE;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISCARCTRL )
            {
                strid = IDS_JOYCALXY_MOVECAR;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISGAMEPAD )
            {
                strid = IDS_JOYCALXY_MOVEGAMEPAD;
            } else
            {
                strid = IDS_JOYCALXY_MOVE;
            }
            break;

        case JCS_XY_CENTER2:
            stridx = CALSTR3;
            if( pcfg->hws.dwFlags & JOY_HWS_ISYOKE )
            {
                strid = IDS_JOYCALXY_CENTERYOKE2;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISCARCTRL )
            {
                strid = IDS_JOYCALXY_CENTERCAR2;
            } else if( pcfg->hws.dwFlags & JOY_HWS_ISGAMEPAD )
            {
                strid = IDS_JOYCALXY_CENTERGAMEPAD2;
            } else
            {
                strid = IDS_JOYCALXY_CENTER2;
            }
            break;

        case JCS_Z_MOVE:
            stridx = CALSTR4;
            if( !(pcfg->hws.dwFlags & JOY_HWS_HASZ) )
            {
                pcv->cState = JCS_R_MOVE;
                done = FALSE;
            } else
            {
                enableCalWindows( pgv, pcfg, hwnd, JC_Z );
                strid = IDS_JOYCALZ_MOVE;
                str2id = getJoyName( pcfg, TRUE );
            }
            break;

        case JCS_Z_PLACEHOLDER:
            pcv->cState = JCS_R_MOVE;
            done = FALSE;
            break;

        case JCS_R_MOVE:
            stridx = CALSTR5;
            if( !(pcfg->hws.dwFlags & JOY_HWS_HASR) && !(pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
            {
                pcv->cState = JCS_U_MOVE;
                done = FALSE;
            } else
            {
                enableCalWindows( pgv, pcfg, hwnd, JC_R );
                strid = IDS_JOYCALRUDDER_MOVE;
                str2id = getJoyName( pcfg, TRUE );
            }
            break;

        case JCS_R_PLACEHOLDER:
            pcv->cState = JCS_U_MOVE;
            done = FALSE;
            break;

        case JCS_U_MOVE:
            stridx = CALSTR6;
            if( !(pcfg->hws.dwFlags & JOY_HWS_HASU) )
            {
                pcv->cState = JCS_V_MOVE;
                done = FALSE;
            } else
            {
                enableCalWindows( pgv, pcfg, hwnd, JC_U );
                strid = IDS_JOYCALU_MOVE;
                str2id = getJoyName( pcfg, TRUE );
            }
            break;

        case JCS_U_PLACEHOLDER:
            pcv->cState = JCS_V_MOVE;
            done = FALSE;
            break;

        case JCS_V_MOVE:
            stridx = CALSTR7;
            if( !(pcfg->hws.dwFlags & JOY_HWS_HASV) )
            {
                pcv->cState = JCS_POV_MOVEUP;
                done = FALSE;
            } else
            {
                enableCalWindows( pgv, pcfg, hwnd, JC_V );
                strid = IDS_JOYCALV_MOVE;
                str2id = getJoyName( pcfg, TRUE );
            }
            break;

        case JCS_V_PLACEHOLDER:
            pcv->cState = JCS_POV_MOVEUP;
            done = FALSE;
            break;

        case JCS_POV_MOVEUP:
            stridx = CALSTR8;
            if( !(pcfg->hws.dwFlags & JOY_HWS_HASPOV) )
            {
                pcv->cState = JCS_FINI;
                done = FALSE;
            } else
            {
                enableCalWindows( pgv, pcfg, hwnd, JC_POV_UP );
                strid = IDS_JOYCALPOV_MOVE;
                str2id = IDS_JOYCAL_UP;
                str3id = getJoyName( pcfg, TRUE );
                str4id = IDS_JOYCAL_UP;
            }
            break;

        case JCS_POV_MOVERIGHT:
            stridx = CALSTR9;
            enableCalWindows( pgv, pcfg, hwnd, JC_POV_RIGHT );
            strid = IDS_JOYCALPOV_MOVE;
            str2id = IDS_JOYCAL_RIGHT;
            str3id = getJoyName( pcfg, TRUE );
            str4id = IDS_JOYCAL_RIGHT;
            break;

        case JCS_POV_MOVEDOWN:
            stridx = CALSTR10;
            enableCalWindows( pgv, pcfg, hwnd, JC_POV_DOWN );
            strid = IDS_JOYCALPOV_MOVE;
            str2id = IDS_JOYCAL_DOWN;
            str3id = getJoyName( pcfg, TRUE );
            str4id = IDS_JOYCAL_DOWN;
            break;

        case JCS_POV_MOVELEFT:
            stridx = CALSTR11;
            enableCalWindows( pgv, pcfg, hwnd, JC_POV_LEFT );
            strid = IDS_JOYCALPOV_MOVE;
            str2id = IDS_JOYCAL_LEFT;
            str3id = getJoyName( pcfg, TRUE );
            str4id = IDS_JOYCAL_LEFT;
            break;

        case JCS_FINI:
            /*
             * see if everything that needs to be calibrated
             * was actually calibrated
             */
            if( !(pcfg->hwv.dwCalFlags & JOY_ISCAL_XY) )
            {
                isdone = FALSE;
            } else if( (pcfg->hws.dwFlags & JOY_HWS_HASZ) &&
                       !(pcfg->hwv.dwCalFlags & JOY_ISCAL_Z) )
            {
                isdone = FALSE;
            } else if( ((pcfg->hws.dwFlags & JOY_HWS_HASR) ||
                        (pcfg->dwUsageSettings & JOY_US_HASRUDDER)) &&
                       !(pcfg->hwv.dwCalFlags & JOY_ISCAL_R) )
            {
                isdone = FALSE;
            } else if( (pcfg->hws.dwFlags & JOY_HWS_HASPOV) &&
                       !(pcfg->hwv.dwCalFlags & JOY_ISCAL_POV) )
            {
                isdone = FALSE;
            } else if( (pcfg->hws.dwFlags & JOY_HWS_HASU) &&
                       !(pcfg->hwv.dwCalFlags & JOY_ISCAL_U) )
            {
                isdone = FALSE;
            } else if( (pcfg->hws.dwFlags & JOY_HWS_HASV) &&
                       !(pcfg->hwv.dwCalFlags & JOY_ISCAL_V) )
            {
                isdone = FALSE;
            } else
            {
                isdone = TRUE;
            }

            strid = ( isdone ) ? IDS_JOYCAL_DONE : IDS_JOYCAL_NOTDONE;

            str2id = getJoyName( pcfg, FALSE );
            str3id = getJoyName( pcfg, TRUE );
            stridx = CALSTR12;
            enableCalWindows( pgv, pcfg, hwnd, JC_FINI );
            rc = FALSE;
            break;
        }  // END OF SWITCH
    }  // END OF WHILE


    // see if there is any OEM text specified
    hinst = GetResourceInstance( );
    assert(hinst);
    hwtext = GetDlgItem( hwnd, IDC_JOYCALMSG );

    if( pcfg->dwUsageSettings & JOY_US_ISOEM )
    {
        LPJOYDATA   pjd;
        pjd = pgv->pjd;
        assert(pjd);
        type = pcfg->dwType - JOY_HW_LASTENTRY;
        if( pjd->oemList[type].cal_strs[ stridx ][0] != 0 )
        {
            SetWindowText( hwtext, pjd->oemList[type].cal_strs[ stridx] );
            return(rc);
        }
    }

    // no OEM text, use the defaults
    LoadString( hinst, strid, str, sizeof(str));

    if( lstrlen(str) )
    {
        if( str2id != -1 )
        {
            NewLoadString( hinst, str2id, str2, sizeof( str2 ) );

            if( str2 )
            {
                if( str3id != -1 )
                {
                    NewLoadString( hinst, str3id, str3, sizeof( str3 ) );

                    if( lstrlen(str3) )
                    {
                        if( str4id != -1 )
                        {
                            NewLoadString( hinst, str4id, str4, sizeof( str4 ) );

                            if( lstrlen(str4) )
                            {
                                // wsprintf( buff, str, str2, str3, str4 );
                                assert(str2);
                                assert(str3);
                                assert(str4);
                                LPSTR lpargs[] = {str2, str3, str4};

                                FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                                              FORMAT_MESSAGE_ARGUMENT_ARRAY,
                                              (LPSTR) str,
                                              0, 0,
                                              buff,
                                              sizeof(buff),
                                              lpargs);

                                SetWindowText( hwtext, buff );
                            }
                        } else
                        {
                            wsprintf( buff, str, str2, str3 );
                            SetWindowText( hwtext, buff );
                        }
                    }
                } else
                {
                    wsprintf( buff, str, str2, str2 );
                    SetWindowText( hwtext, buff );
                }
            }
        } else
        {
            SetWindowText( hwtext, str );
        }
    }

    return(rc);

} /* joyCalStateChange */

/*
 * joyCalStateSkip - skip the current state, move to the next one
 */
static void joyCalStateSkip( LPCALVARS pcv, HWND hwnd )
{

    assert(pcv);
    assert(hwnd);

#if 0
    /*
     * if we're calibrating XY, skip to Z
     */
    if( pcv->cState <= JCS_XY_CENTER2 )
    {
        pcv->cState = JCS_XY_CENTER2;
        /*
         * if we're calibrating Z, skip to R
         */
    } else if( pcv->cState < JCS_Z_PLACEHOLDER )
    {
        pcv->cState = JCS_Z_PLACEHOLDER;
        /*
         * if we're calibrating R, skip to U
         */
    } else if( pcv->cState < JCS_R_PLACEHOLDER )
    {
        pcv->cState = JCS_R_PLACEHOLDER;
        /*
         * if we're calibrating U, skip to V
         */
    } else if( pcv->cState < JCS_U_PLACEHOLDER )
    {
        pcv->cState = JCS_U_PLACEHOLDER;
        /*
         * if we're calibrating V, skip to POV
         */
    } else if( pcv->cState < JCS_V_PLACEHOLDER )
    {
        pcv->cState = JCS_V_PLACEHOLDER;
        /*
         * we must be calibration POV, skip to the end
         */
    } else
    {
        pcv->cState = JCS_POV_MOVELEFT;
    }
#endif

    /*
     * state changed, reset to the new one
     */
    CauseRedraw( &pcv->ji, FALSE );
    joyCalStateChange( pcv, hwnd, TRUE );

} /* joyCalStateSkip */

/*
 * resetCustomPOVFlags - set POV flags based on original values for custom joystick
 */
static void resetCustomPOVFlags( LPGLOBALVARS pgv, LPJOYREGHWCONFIG pcfg )
{
    assert(pgv);
    assert(pcfg);

    if( pcfg->dwType == JOY_HW_CUSTOM )
    {
        pcfg->hws.dwFlags &= ~(JOY_HWS_POVISPOLL|JOY_HWS_POVISBUTTONCOMBOS);

        if( pgv->bOrigPOVIsPoll )
            pcfg->hws.dwFlags |= JOY_HWS_POVISPOLL;

        if( pgv->bOrigPOVIsButtonCombos )
            pcfg->hws.dwFlags |= JOY_HWS_POVISBUTTONCOMBOS;
    }

} /* resetCustomPOVFlags */


/*
 * joyCalStateBack - move back to start the previous state
 */
static void joyCalStateBack( LPCALVARS pcv, HWND hwnd )
{
    assert(pcv);
    assert(hwnd);

    BOOL        back;
    LPJOYREGHWCONFIG    pcfg;
    LPGLOBALVARS    pgv;

    pgv = pcv->pgv;
    assert(pgv);
    back = TRUE;
    pcfg = &pgv->joyHWCurr;
    assert(pcfg);
    /*
     * at the end, backup
     */
    if( pcv->cState == JCS_FINI )
    {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
        SetFocus(GetDlgItem(hwnd, IDC_JOYCALBACK));
// END ADD

        /*
         * if there is POV, back up to it
         */
        if( pcfg->hws.dwFlags & JOY_HWS_HASPOV )
        {
            pcv->cState = JCS_V_PLACEHOLDER;
            resetCustomPOVFlags( pgv, pcfg );
            /*
             * if there is V, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASV )
        {
            pcv->cState = JCS_U_PLACEHOLDER;
            /*
             * if there is U, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASU )
        {
            pcv->cState = JCS_R_PLACEHOLDER;
            /*
             * if there is R, back up to it
             */
        } else if( (pcfg->hws.dwFlags & JOY_HWS_HASR) || (pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
        {
            pcv->cState = JCS_Z_PLACEHOLDER;
            /*
             * if there is Z, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        {
            pcv->cState = JCS_XY_CENTER2;
            /*
             * no where else to go, back up to XY
             */
        } else
        {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
            SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
// END ADD
//            pcv->cState = JCS_INIT;
            pcv->cState = JCS_XY_MOVE;
//            back = FALSE;
        }
        /*
         * doing POV, so restart it
         */
    } else if( pcv->cState > JCS_POV_MOVEUP )
    {
        pcv->cState = JCS_V_PLACEHOLDER;
//	pcfg->hws.dwFlags &= ~(JOY_HWS_POVISPOLL|JOY_HWS_POVISBUTTONCOMBOS);
        resetCustomPOVFlags( pgv, pcfg );
        /*
         * just starting POV, back up
         */
    } else if( pcv->cState == JCS_POV_MOVEUP )
    {

// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
        SetFocus(GetDlgItem(hwnd, IDC_JOYCALBACK));
// END ADD

        /*
         * if there is V, back up to it
         */
        if( pcfg->hws.dwFlags & JOY_HWS_HASV )
        {
            pcv->cState = JCS_U_PLACEHOLDER;
            /*
             * if there is U, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASU )
        {
            pcv->cState = JCS_R_PLACEHOLDER;
            /*
             * if there is R, back up to it
             */
        } else if( (pcfg->hws.dwFlags & JOY_HWS_HASR) ||
                   (pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
        {
            pcv->cState = JCS_Z_PLACEHOLDER;
            /*
             * if there is Z, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        {
            pcv->cState = JCS_XY_CENTER2;
            /*
             * no where else to go, back up to XY
             */
        } else
        {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
            SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
// END ADD
//            pcv->cState = JCS_INIT;
            pcv->cState = JCS_XY_MOVE;
//            back = FALSE;
        }
        /*
         * doing V, backup
         */
    } else if( pcv->cState == JCS_V_MOVE )
    {
        /*
         * if there is U, back up to it
         */
        if( pcfg->hws.dwFlags & JOY_HWS_HASU )
        {
            pcv->cState = JCS_R_PLACEHOLDER;
            /*
             * if there is R, back up to it
             */
        } else if( (pcfg->hws.dwFlags & JOY_HWS_HASR) ||
                   (pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
        {
            pcv->cState = JCS_Z_PLACEHOLDER;
            /*
             * if there is Z, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        {
            pcv->cState = JCS_XY_CENTER2;
            /*
             * no where else to go, back up to XY
             */
        } else
        {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
            SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
// END ADD
//            pcv->cState = JCS_INIT;
            pcv->cState = JCS_XY_MOVE;
//            back = FALSE;
        }
        /*
         * doing U, backup
         */
    } else if( pcv->cState == JCS_U_MOVE )
    {
        /*
         * if there is R, back up to it
         */
        if( (pcfg->hws.dwFlags & JOY_HWS_HASR) ||
            (pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
        {
            pcv->cState = JCS_Z_PLACEHOLDER;
            /*
             * if there is Z, back up to it
             */
        } else if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        {
            pcv->cState = JCS_XY_CENTER2;
            /*
             * no where else to go, back up to XY
             */
        } else
        {
//            pcv->cState = JCS_INIT;
            pcv->cState = JCS_XY_MOVE;
//            back = FALSE;
        }
        /*
         * doing R, backup
         */
    } else if( pcv->cState == JCS_R_MOVE )
    {
        /*
         * if there is Z, back up to it
         */
        if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        {
            pcv->cState = JCS_XY_CENTER2;
            /*
             * no where else to go, back up to XY
             */
        } else
        {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
            SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
// END ADD
//          pcv->cState = JCS_INIT;
            pcv->cState = JCS_XY_MOVE;
//            back = FALSE;
        }
        /*
         * if we're doing Z or in the middle of XY, backup to XY
         */
    } else if( pcv->cState == JCS_XY_MOVE )
    {
        SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
        pcv->cState = JCS_INIT;
        back = FALSE;
    } else
    {
// ADDED BY CML 6/21/96 TO FIX LOST FOCUS BUG (FLASH RAID 167)
        SetFocus(GetDlgItem(hwnd, IDC_JOYCALNEXT));
// END ADD
        pcv->cState = (cal_states)(pcv->cState - 2);
//        pcv->cState = JCS_INIT;
//        back = FALSE;
    }

    /*
     * state changed, reset to the new one
     */
    CauseRedraw( &pcv->ji, FALSE );
    joyCalStateChange( pcv, hwnd, back );

} /* joyCalStateBack */

// macro to get new max/min data for an axis
#define NEWMINMAX( a ) \
    if( pji->dw##a##pos > pcv->jr.jpMax.dw##a ) { \
	pcv->jr.jpMax.dw##a = pji->dw##a##pos; \
    } \
    if( pji->dw##a##pos < pcv->jr.jpMin.dw##a ) { \
	pcv->jr.jpMin.dw##a = pji->dw##a##pos; \
    } \
    pji->dw##a##pos *= RAW_SHIFT;

// joyCollectCalInfo - record calibration info 
static BOOL joyCollectCalInfo( LPCALVARS pcv, HWND hwnd, LPJOYINFOEX pji )
{
    assert(pcv);
    assert(hwnd);
    assert(pji);

    LPGLOBALVARS    pgv;
    LPJOYREGHWCONFIG    pcfg;

    pgv = pcv->pgv;
    assert(pgv);
    switch( pcv->cState )
    {
    // remember XY center
    case JCS_XY_CENTER1:
    case JCS_XY_CENTER2:
#ifdef _DEBUG
        TRACE("%s: %d: dwXpos is %d dwYpos is %d\n", __FILE__, __LINE__, pji->dwXpos, pji->dwYpos);
#endif // _DEBUG
        if( !pcv->jr.jpCenter.dwY )
        {
            pgv->joyRange.jpMax.dwY = pji->dwYpos << 1;
            pcv->jr.jpCenter.dwY    = pji->dwYpos;
        }

        if( !pcv->jr.jpCenter.dwX )
        {
            pgv->joyRange.jpMax.dwX = pji->dwXpos << 1;
            pcv->jr.jpCenter.dwX    = pji->dwXpos;
        }

        pgv->joyRange.jpMin.dwX = 0;
        pgv->joyRange.jpMin.dwY = 0;

        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWXY );
        break;

        // remember max/min XY values
    case JCS_XY_MOVE:
        if( pji->dwXpos > pcv->jr.jpMax.dwX )
        {
            pcv->jr.jpMax.dwX = pji->dwXpos;
        }

        if( pji->dwXpos < pcv->jr.jpMin.dwX )
        {
            pcv->jr.jpMin.dwX = pji->dwXpos;
        }

        if( pji->dwYpos > pcv->jr.jpMax.dwY )
        {
            pcv->jr.jpMax.dwY = pji->dwYpos;
        }

        if( pji->dwYpos < pcv->jr.jpMin.dwY )
        {
            pcv->jr.jpMin.dwY = pji->dwYpos;
        }

        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWXY );
        break;

        // remember max/min Z value
    case JCS_Z_MOVE:
        NEWMINMAX( Z );
        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWZ );
        break;

        // remember max/min R value
    case JCS_R_MOVE:
        NEWMINMAX( R );
        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWR );
        break;

        // remember max/min U value
    case JCS_U_MOVE:
        NEWMINMAX( U );
        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWU );
        break;

        // remember max/min V value
    case JCS_V_MOVE:
        NEWMINMAX( V );
        DoJoyMove( pgv, hwnd, pji, &pcv->ji, JOYMOVE_DRAWV );
        break;
    }

    // if a button was pressed, move to the next state
    if( ((pcv->ji.dwButtons & ALL_BUTTONS) != (pji->dwButtons & ALL_BUTTONS)) &&
        ((pji->dwButtons & JOY_BUTTON1)  ||
         (pji->dwButtons & JOY_BUTTON2)  ||
         (pji->dwButtons & JOY_BUTTON3)  ||
         (pji->dwButtons & JOY_BUTTON4)  ||
         (pji->dwButtons & JOY_BUTTON5)  ||
         (pji->dwButtons & JOY_BUTTON6)  ||
         (pji->dwButtons & JOY_BUTTON7)  ||
         (pji->dwButtons & JOY_BUTTON8)  ||
         (pji->dwButtons & JOY_BUTTON9)  ||
         (pji->dwButtons & JOY_BUTTON10) ||
         (pji->dwButtons & JOY_BUTTON11) ||
         (pji->dwButtons & JOY_BUTTON12) ||
         (pji->dwButtons & JOY_BUTTON13) ||
         (pji->dwButtons & JOY_BUTTON14) ||
         (pji->dwButtons & JOY_BUTTON15) ||
         (pji->dwButtons & JOY_BUTTON16) ||
         (pji->dwButtons & JOY_BUTTON17) ||
         (pji->dwButtons & JOY_BUTTON18) ||
         (pji->dwButtons & JOY_BUTTON19) ||
         (pji->dwButtons & JOY_BUTTON20) ||
         (pji->dwButtons & JOY_BUTTON21) ||
         (pji->dwButtons & JOY_BUTTON22) ||
         (pji->dwButtons & JOY_BUTTON23) ||
         (pji->dwButtons & JOY_BUTTON24) ||
         (pji->dwButtons & JOY_BUTTON25) ||
         (pji->dwButtons & JOY_BUTTON26) ||
         (pji->dwButtons & JOY_BUTTON27) ||
         (pji->dwButtons & JOY_BUTTON28) ||
         (pji->dwButtons & JOY_BUTTON29) ||
         (pji->dwButtons & JOY_BUTTON30) ||
         (pji->dwButtons & JOY_BUTTON31) ||
         (pji->dwButtons & JOY_BUTTON32) ) )
    {
        // check and see if we are leaving one calibration to the next;
        // if yes, take time to stop and remember what the user just did
        pcfg = &pgv->joyHWCurr;
        assert(pcfg);

        switch( pcv->cState )
        {
        case JCS_XY_CENTER1:
            pcv->jr.jpCenter.dwX = pji->dwXpos;
            pcv->jr.jpCenter.dwY = pji->dwYpos;
            DPF( "Center 1: %d,%d\r\n", pji->dwXpos, pji->dwYpos );
            break;

        case JCS_XY_CENTER2:
            DPF( "Center 2: %d,%d\r\n", pji->dwXpos, pji->dwYpos );
            pcv->jr.jpCenter.dwX += pji->dwXpos;
            pcv->jr.jpCenter.dwY += pji->dwYpos;
            pcv->jr.jpCenter.dwX /= 2;
            pcv->jr.jpCenter.dwY /= 2;
            DPF( "Center Avg: %d,%d\r\n", pcv->jr.jpCenter.dwX, pcv->jr.jpCenter.dwY );
            pcfg->hwv.jrvHardware.jpMin.dwX = pcv->jr.jpMin.dwX;
            pcfg->hwv.jrvHardware.jpMin.dwY = pcv->jr.jpMin.dwY;
            pcfg->hwv.jrvHardware.jpMax.dwX = pcv->jr.jpMax.dwX;
            pcfg->hwv.jrvHardware.jpMax.dwY = pcv->jr.jpMax.dwY;
            pcfg->hwv.jrvHardware.jpCenter.dwX = pcv->jr.jpCenter.dwX;
            pcfg->hwv.jrvHardware.jpCenter.dwY = pcv->jr.jpCenter.dwY;
            pcfg->hwv.dwCalFlags |= JOY_ISCAL_XY;
            break;
        case JCS_Z_MOVE:
            pcfg->hwv.jrvHardware.jpMin.dwZ = pcv->jr.jpMin.dwZ;
            pcfg->hwv.jrvHardware.jpMax.dwZ = pcv->jr.jpMax.dwZ;
            pcfg->hwv.dwCalFlags |= JOY_ISCAL_Z;
            break;
        case JCS_R_MOVE:
            pcfg->hwv.jrvHardware.jpMin.dwR = pcv->jr.jpMin.dwR;
            pcfg->hwv.jrvHardware.jpMax.dwR = pcv->jr.jpMax.dwR;
            pcfg->hwv.dwCalFlags |= JOY_ISCAL_R;
            break;
        case JCS_U_MOVE:
            pcfg->hwv.jrvHardware.jpMin.dwU = pcv->jr.jpMin.dwU;
            pcfg->hwv.jrvHardware.jpMax.dwU = pcv->jr.jpMax.dwU;
            pcfg->hwv.dwCalFlags |= JOY_ISCAL_U;
            break;
        case JCS_V_MOVE:
            pcfg->hwv.jrvHardware.jpMin.dwV = pcv->jr.jpMin.dwV;
            pcfg->hwv.jrvHardware.jpMax.dwV = pcv->jr.jpMax.dwV;
            pcfg->hwv.dwCalFlags |= JOY_ISCAL_V;
            break;
        }

        pcv->ji.dwButtons = pji->dwButtons;
        return(joyCalStateChange( pcv, hwnd, TRUE ));
    }
    pcv->ji.dwButtons = pji->dwButtons;
    return(TRUE);

} /* joyCollectCalInfo */

/*
 * joyCalibrateInitDialog - init the calibration dialog
 */
static BOOL joyCalibrateInitDialog( HWND hwnd, LPARAM lParam )
{
    LPJOYREGHWCONFIG    pcfg;
    LPCALVARS       pcv = NULL;
    LPGLOBALVARS    pgv = NULL;

    ASSERT (::IsWindow(hwnd));

    // set up calibration variables
    pcv = (CALVARS *) DoAlloc( sizeof( CALVARS ) );
    ASSERT(pcv);

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

    SetWindowLong( hwnd, DWL_USER, (LONG) pcv );
    assert(pcv);

    pgv = (LPGLOBALVARS) lParam;
    assert(pgv);

    pcv->pgv = pgv;

    //
    // set labels
    //
    LPSTR psz1 = new char[MAX_STR_LEN];
    ASSERT (psz1);

    LPSTR psz2 = new char[MAX_STR_LEN];
    ASSERT (psz2);

    if( !LoadString(GetResourceInstance(), IDS_JOYCALCAPN, psz1, MAX_STR_LEN) )
    {
        TRACE( "%s: %s - LoadString Failure!\n", __FILE__, __LINE__);
    }

    wsprintf(psz2, psz1, pgv->iJoyId+1);
    SetWindowText(hwnd, psz2);

    if( psz1 )
        delete[] (psz1);

    if( psz2 )
        delete[] (psz2);


    // init state info
    pcv->cState = JCS_INIT;

    // set dialog text based on OEM strings
    SetOEMText( pgv, hwnd, FALSE );

    // customize dialog based on Z axis, R axis, and POV hat
    pcfg = &pgv->joyHWCurr;
    assert(pcfg);

    pcv->iAxisCount = 2;

    if( pcfg->hws.dwFlags & JOY_HWS_HASZ )
        pcv->iAxisCount++;

    if( (pcfg->hws.dwFlags & JOY_HWS_HASR) || (pcfg->dwUsageSettings & JOY_US_HASRUDDER) )
        pcv->iAxisCount++;

    if( (pcfg->hws.dwFlags & JOY_HWS_HASPOV) && (pcfg->hws.dwFlags & JOY_HWS_POVISPOLL) )
        pcv->iAxisCount++;

    if( pcfg->hws.dwFlags & JOY_HWS_HASU )
        pcv->iAxisCount++;

    if( pcfg->hws.dwFlags & JOY_HWS_HASV )
        pcv->iAxisCount++;

    ShowControls( pcfg, hwnd );

    HWND hCtrl = GetDlgItem( hwnd, IDC_JOYPOV_LABEL );
    ASSERT (::IsWindow(hCtrl));
    ShowWindow(hCtrl, pcfg->hws.dwFlags & JOY_HWS_HASPOV ? SW_SHOW : SW_HIDE);

    // if all axes are used and we have POV then it MUST be buttons
    if( pcfg->hws.dwFlags & JOY_HWS_HASPOV )
    {
        if( pgv->dwMaxAxes == 4 && pcv->iAxisCount == 4 )
            pcfg->hws.dwFlags |= JOY_HWS_POVISBUTTONCOMBOS;
    }

    // other misc setup
    pcv->bPOVdone  = FALSE;
    pcv->bHasTimer = SetTimer( hwnd, TIMER_ID, JOYPOLLTIME, NULL );
    pcv->bUseTimer = TRUE;

    if( !pcv->bHasTimer )
    {
        DPF( "No timer for joystick calibration!\r\n" );
        return(FALSE);
    }

    if( !joyCalStateChange( pcv, hwnd, FALSE ) )
    {
        DPF( "Could not initialize joystick calibration\r\n" );
        return(FALSE);
    }

    return(TRUE);

} /* joyCalibrateInitDialog */

/*
 * setJIFlagsForPOV - get joyinfo flags to allow a raw POV poll
 */
static void setJIFlagsForPOV( LPCALVARS pcv, LPJOYREGHWCONFIG pcfg, DWORD *pflags )
{
    /*
     * for polled POV, we need to specifiy JOY_CAL_READ(3|4) to make
     * the driver give us position values back instead of trying to
     * give us a POV value back
     */
//    if( pcfg->hws.dwFlags & JOY_HWS_HASPOV ) 
    {
        if( pcfg->hws.dwFlags & JOY_HWS_POVISPOLL )
        {
            switch( pcv->iAxisCount )
            {
            case 6:
                (*pflags) |= JOY_CAL_READ6;
                break;
            case 5:
                (*pflags) |= JOY_CAL_READ5;
                break;

            case 4: 
                (*pflags) |= JOY_CAL_READ4;
                break;

            case 3: 
                (*pflags) |= JOY_CAL_READ3;
                break;
            }
            // If we don't have a 3rd or 4th axis on this joystick, try reading
            // another axis anyway to see if the POV hat is on it
        } else if( !(pcfg->hws.dwFlags & (JOY_HWS_POVISPOLL|JOY_HWS_POVISBUTTONCOMBOS)) )
        {
            switch( pcv->iAxisCount )
            {
            case 5:
                (*pflags) |= JOY_CAL_READ6;
                break;

            case 4:
                (*pflags) |= JOY_CAL_READ5;
                break;

            case 3:
                (*pflags) |= JOY_CAL_READ4;
                break;

            case 2:
                (*pflags) |= JOY_CAL_READ3;
                break;
            }
        }
    }

} /* setJIFlagsForPOV */

// tryPOV - try for a POV access
static BOOL tryPOV( LPCALVARS pcv, HWND hwnd )
{
    assert(pcv);
    assert(hwnd);

    int         rc;
    BOOL        ispoll;
    BOOL        isb;
    BOOL        nowaypoll;
    JOYINFOEX       ji;
    DWORD       val;
    LPJOYREGHWCONFIG    pcfg;
    LPGLOBALVARS    pgv;
    int         i;

    pgv = pcv->pgv;
    assert(pgv);

    // reject call if not in a POV state
    if( !(pcv->cState == JCS_POV_MOVEUP ||  pcv->cState == JCS_POV_MOVEDOWN ||
          pcv->cState == JCS_POV_MOVELEFT ||  pcv->cState == JCS_POV_MOVERIGHT) )
        return(FALSE);

    // take a snapshot of the current joystick state
    pcfg = &pgv->joyHWCurr;
    assert(pcfg);
    nowaypoll = FALSE;
    ji.dwSize = sizeof( ji );
    while( 1 )
    {
        // get joystick info
        ji.dwFlags = JOY_CALIB_FLAGS;

        // if you have a POV, set the flags for it!
        if( pcfg->hws.dwFlags & JOY_HWS_HASPOV )
            setJIFlagsForPOV( pcv, pcfg, &ji.dwFlags );

        rc = joyGetPosEx( pgv->iJoyId, &ji );
        if( rc == JOYERR_NOERROR )
            break;

        if( !(pcfg->hws.dwFlags & JOY_HWS_POVISPOLL) && (ji.dwFlags & (JOY_CAL_READ3|JOY_CAL_READ4|JOY_CAL_READ5|JOY_CAL_READ6)) )
        {
            // try again, but don't ask for extra axis
            ji.dwFlags &= ~(JOY_CAL_READ6 | JOY_CAL_READ5 | JOY_CAL_READ4 | JOY_CAL_READ3);
            rc = joyGetPosEx( pgv->iJoyId, &ji );
            if( rc == JOYERR_NOERROR )
            {
                nowaypoll = TRUE;   // pov can't possibly be polled
                break;
            } else return(JoyError( hwnd )) ? TRUE : FALSE;  // have to wait for next "Select POV" to retry
        } else return(JoyError( hwnd )) ? TRUE : FALSE;  // have to wait for next "Select POV" to retry
    }

    /*
     * here is where we determine if POV is polled or is button combos.
     *
     * See if we already know the answer (bits in joyHWCurr):
     *     if yes:
     *	       we're done.
     *     if no:
     *         We see if there are currently multiple buttons down.
     *         if yes:
     *             POV is assumed to be button combos.
     *         if no:
     *             POV is assumed to be done with polling
     */
    ispoll = FALSE;
    isb = FALSE;
    if( pcfg->hws.dwFlags & JOY_HWS_POVISPOLL )
    {
        ispoll = TRUE;
    } else if( pcfg->hws.dwFlags & JOY_HWS_POVISBUTTONCOMBOS )
    {
        isb = TRUE;
    }

    if( !isb && !ispoll )
    {
        // the type is indeterminate, so we identify it 
        if( nowaypoll ||((ji.dwButtons != 0) && 
                         (ji.dwButtons != JOY_BUTTON1)    &&
                         (ji.dwButtons != JOY_BUTTON2)    && 
                         (ji.dwButtons != JOY_BUTTON3)    &&
                         (ji.dwButtons != JOY_BUTTON4)    &&
                         (ji.dwButtons != JOY_BUTTON5)    &&
                         (ji.dwButtons != JOY_BUTTON6)    &&
                         (ji.dwButtons != JOY_BUTTON7)    &&
                         (ji.dwButtons != JOY_BUTTON8)    &&
                         (ji.dwButtons != JOY_BUTTON9)    &&
                         (ji.dwButtons != JOY_BUTTON10)   &&
                         (ji.dwButtons != JOY_BUTTON11)   &&
                         (ji.dwButtons != JOY_BUTTON12)   &&
                         (ji.dwButtons != JOY_BUTTON13)   &&
                         (ji.dwButtons != JOY_BUTTON14)   &&
                         (ji.dwButtons != JOY_BUTTON15)   &&
                         (ji.dwButtons != JOY_BUTTON16)   &&
                         (ji.dwButtons != JOY_BUTTON17)   &&
                         (ji.dwButtons != JOY_BUTTON18)   &&
                         (ji.dwButtons != JOY_BUTTON19)   &&
                         (ji.dwButtons != JOY_BUTTON20)   &&
                         (ji.dwButtons != JOY_BUTTON21)   &&
                         (ji.dwButtons != JOY_BUTTON22)   &&
                         (ji.dwButtons != JOY_BUTTON23)   &&
                         (ji.dwButtons != JOY_BUTTON24)   &&
                         (ji.dwButtons != JOY_BUTTON25)   &&
                         (ji.dwButtons != JOY_BUTTON26)   &&
                         (ji.dwButtons != JOY_BUTTON27)   &&
                         (ji.dwButtons != JOY_BUTTON28)   &&
                         (ji.dwButtons != JOY_BUTTON29)   &&
                         (ji.dwButtons != JOY_BUTTON30)   &&
                         (ji.dwButtons != JOY_BUTTON31)   &&
                         (ji.dwButtons != JOY_BUTTON32)   ) )
        {
            isb = TRUE;
            pcfg->hws.dwFlags |= JOY_HWS_POVISBUTTONCOMBOS;
        } else
        {
            // we always assume J2 Y for a polling POV if unspecified
            ispoll = TRUE;
            pcfg->hws.dwFlags |= JOY_HWS_POVISPOLL;
        }

        // the driver needs to notified that we've made this decision
        RegSaveCurrentJoyHW( pgv );
        RegistryUpdated( pgv );
    }

    // record the data value for this POV reading
    if( isb )
        val = ji.dwButtons;
    else
        val = (pcfg->hws.dwFlags & JOY_HWS_HASZ) ? ji.dwRpos : ji.dwZpos;

    switch( pcv->cState )
    {
    case JCS_POV_MOVEUP:
        pcv->pov[JOY_POVVAL_FORWARD]  = val;
        break;

    case JCS_POV_MOVERIGHT:
        pcv->pov[JOY_POVVAL_RIGHT]    = val;
        break;

    case JCS_POV_MOVEDOWN:
        pcv->pov[JOY_POVVAL_BACKWARD] = val;
        break;

    case JCS_POV_MOVELEFT:
        pcv->pov[JOY_POVVAL_LEFT]     = val;

        // since this was the last POV thing to calibrate, we need to save the calibration info
        for( i=0;i<JOY_POV_NUMDIRS;i++ )
            pcfg->hwv.dwPOVValues[i] = pcv->pov[i];

        pcfg->hwv.dwCalFlags |= JOY_ISCAL_POV;
        pcv->bPOVdone = TRUE;
        break;
    }
    return(joyCalStateChange( pcv, hwnd, TRUE ));

} /* tryPOV */

#ifdef DEAD_CODE
// FixCustomPOVType - fix custom POV type info if POV wasn't calibrated;  called by test dlg to update config
void FixCustomPOVType( LPCALVARS pcv )
{
    assert(pcv);

    if( !pcv->bPOVdone )
        resetCustomPOVFlags( pcv->pgv, &pcv->pgv->joyHWCurr );

} /* FixCustomPOVType */
#endif // DEAD_CODE


// CalibrateProc - calibrate a joystick
BOOL CALLBACK CalibrateProc( HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
    switch( umsg )
    {
    case WM_CONTEXTMENU:
        OnContextMenu(wParam);
        return(1);

    case WM_HELP:
        OnHelp(lParam);
        return(1);

    case WM_TIMER:
        {
            LPCALVARS  pcv = (LPCALVARS) GetWindowLong( hwnd, DWL_USER );
            assert(pcv);

            if( pcv->bUseTimer )
            {
                JOYINFOEX           *ji = new JOYINFOEX;
                ASSERT (ji);

                MMRESULT            rc;
                LPJOYREGHWCONFIG    pcfg;
                LPGLOBALVARS        pgv;

                pgv = pcv->pgv;
                assert(pgv);

                pcv->bUseTimer = FALSE;
                ji->dwSize = sizeof( JOYINFOEX );

                while( 1 )
                {
                    // get current joystick info
                    ji->dwFlags = JOY_CALIB_FLAGS;
                    pcfg = &pgv->joyHWCurr;
                    ASSERT(pcfg);

                    // if there is a POV, set the flags for it!
                    if( pcfg->hws.dwFlags & JOY_HWS_HASPOV )
                        setJIFlagsForPOV( pcv, pcfg, &ji->dwFlags );

                    rc = joyGetPosEx( pgv->iJoyId, ji );

                    if( rc == JOYERR_NOERROR )
                        break;

                    // didn't work, try without extra POV axis
                    if( !(pcfg->hws.dwFlags & JOY_HWS_POVISPOLL) && (ji->dwFlags & (JOY_CAL_READ3|JOY_CAL_READ4|JOY_CAL_READ5|JOY_CAL_READ6)) )
                    {
                        ji->dwFlags &= ~(JOY_CAL_READ6 | JOY_CAL_READ5 | JOY_CAL_READ4 | JOY_CAL_READ3);

                        rc = joyGetPosEx( pgv->iJoyId, ji );

                        if( rc == JOYERR_NOERROR )
                            break;
                    }

                    if( !JoyError( hwnd ) )
                    {
                        // return now if cancel selected; don't turn back on the timer
                        PostMessage(hwnd, WM_COMMAND, IDCANCEL, 0); // ADDED CML 7/05/96
                        return(FALSE);   
                    }
                    continue;
                }

// how could you get here and rc != JOYERR_NOERROR?
                if( rc == JOYERR_NOERROR )
                    joyCollectCalInfo( pcv, hwnd, ji );

                if( ji )
                    delete (ji);

                /*
                 * If we've started POV calibration, we need to look at the
                 * keyboard and ignore joystick, so don't turn the timer
                 * back on if we've started the POV calibration
                 */
                if( pcv->cState < JCS_POV_MOVEUP )
                    pcv->bUseTimer = TRUE;
            }
            break;
        }


    case WM_DESTROY:
        {
            LPCALVARS   pcv = (LPCALVARS) GetWindowLong( hwnd, DWL_USER );
            assert(pcv);
            DoFree( pcv );
            break;
        }

    case WM_INITDIALOG:
        if( !joyCalibrateInitDialog( hwnd, lParam ) )
        {
            LPCALVARS pcv = (LPCALVARS) GetWindowLong( hwnd, DWL_USER );
            assert(pcv);

            if( pcv != NULL && pcv->bHasTimer )
            {
                KillTimer( hwnd, TIMER_ID );
                pcv->bHasTimer = FALSE;
            }

            EndDialog( hwnd, 0 );
        }
        return(FALSE);

    case WM_PAINT:
        {
            LPCALVARS   pcv = (LPCALVARS) GetWindowLong( hwnd, DWL_USER );
            assert(pcv);
            CauseRedraw( &pcv->ji, FALSE );
            return(FALSE);
        }

    case WM_COMMAND:
        {
            LPCALVARS pcv = (LPCALVARS) GetWindowLong( hwnd, DWL_USER );
            assert(pcv);
            int id = GET_WM_COMMAND_ID(wParam, lParam);

            switch( id )
            {
#ifdef DEAD_CODE			
            case IDC_JOYTEST:
                {
                    BOOL        timeon;

                    timeon = pcv->bUseTimer;
                    pcv->bUseTimer = FALSE;
                    DoTest( pcv->pgv, hwnd, (void (__cdecl *)(void *)) FixCustomPOVType, pcv );
                    pcv->bUseTimer = timeon;
                    break;
                }
#endif //DEAD_CODE

            case IDCANCEL:
                // fall through
            case IDC_JOYCALDONE:
                if( pcv->bHasTimer )
                {
                    KillTimer( hwnd, TIMER_ID );
                    pcv->bHasTimer = FALSE;
                }

//			    {
//				LPJOYREGHWCONFIG	pcfg = &pcv->pgv->joyHWCurr;
//			    assert(pcfg);
//			    }
                EndDialog( hwnd, (id == IDC_JOYCALDONE) );
                break;

            case IDC_JOYPICKPOV:
                if( !tryPOV( pcv, hwnd ) )
                {
                    ASSERT (::IsWindow(hwnd));
                    HWND    hwb = GetDlgItem( hwnd, IDC_JOYPICKPOV );
                    ASSERT (::IsWindow(hwb));

                    ShowWindow( hwb, SW_HIDE );
                    EnableWindow( hwb, FALSE );
                }
                break;

            case IDC_JOYCALNEXT:
                pcv->bUseTimer = TRUE;
                joyCalStateSkip( pcv, hwnd );
                break;

            case IDC_JOYCALBACK:
                pcv->bUseTimer = TRUE;
                joyCalStateBack( pcv, hwnd );
                break;

            default:
                break;
            }
            break;
        }

    default:
        break;
    }
    return(FALSE);

} // CalibrateProc

// DoCalibrate - do the calibration dialog
void DoCalibrate( LPGLOBALVARS pgv, HWND hwnd )
{
    assert(pgv);

    JOYREGHWCONFIG  save_joycfg;
    int         rc;
    int         id;

    // save the current config, and then add the rudder if it is present
    save_joycfg = pgv->joyHWCurr;

    // if this is a custom joystick, then don't assume anything
    // about how the POV is set up
    if( pgv->joyHWCurr.dwType == JOY_HW_CUSTOM )
    {
        pgv->bOrigPOVIsPoll = (pgv->joyHWCurr.hws.dwFlags & JOY_HWS_POVISPOLL);
        pgv->bOrigPOVIsButtonCombos = (pgv->joyHWCurr.hws.dwFlags & JOY_HWS_POVISBUTTONCOMBOS);
        pgv->joyHWCurr.hws.dwFlags &= ~(JOY_HWS_POVISPOLL|JOY_HWS_POVISBUTTONCOMBOS);
    }

    // update the registry with our new joystick info
    RegSaveCurrentJoyHW( pgv );
    RegistryUpdated( pgv );

    // Fix for 8738, missing IDD_JOYCALIBRATE1 in resource table!
    id = ( pgv->joyHWCurr.hws.dwFlags & (JOY_HWS_HASU|JOY_HWS_HASV)) ? IDD_JOYCALIBRATE1 : IDD_CAL;

    rc = DialogBoxParam(GetResourceInstance(), MAKEINTRESOURCE( id ), hwnd, 
                        (int (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long)) CalibrateProc, (LONG) pgv );

    // update the registry with the new info or the old info
    if( rc )
        PropSheet_Changed( GetParent(hwnd), hwnd );
    else
        pgv->joyHWCurr = save_joycfg;

    RegSaveCurrentJoyHW( pgv );
    RegistryUpdated( pgv );

} /* DoCalibrate */


////////////////////////////////////////////////////////////////////////////////////////
//	OnContextMenu(WPARAM wParam)
////////////////////////////////////////////////////////////////////////////////////////
void OnContextMenu(WPARAM wParam)
{
    short nSize = STR_LEN_32;

    // point to help file
    char *pszHelpFileName = new char[nSize];
    ASSERT (pszHelpFileName);                      

    // returns help file name and size of string
    GetHelpFileName(pszHelpFileName, &nSize);

    WinHelp((HWND)wParam, pszHelpFileName, HELP_CONTEXTMENU, (DWORD)CalibrateHelpIDs);

    if( pszHelpFileName ) delete[] (pszHelpFileName);
}

////////////////////////////////////////////////////////////////////////////////////////
//	OnHelp(LPARAM lParam)
////////////////////////////////////////////////////////////////////////////////////////
void OnHelp(LPARAM lParam)
{
    short nSize = STR_LEN_32;

    // point to help file
    char *pszHelpFileName = new char[nSize];
    ASSERT (pszHelpFileName);

    // returns help file name and size of string
    GetHelpFileName(pszHelpFileName, &nSize);

    LPHELPINFO lphi = (LPHELPINFO)lParam;
    if( lphi->iContextType==HELPINFO_WINDOW )
        WinHelp((HWND)lphi->hItemHandle, pszHelpFileName, HELP_WM_HELP, (DWORD)CalibrateHelpIDs);

    if( pszHelpFileName ) delete[] (pszHelpFileName);
}



