/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    wizard.c

Abstract:

    Send fax wizard dialogs

Environment:

    Fax driver user interface

Revision History:

    01/19/96 -davidx-
        Created it.

    mm/dd/yy -author-
        description

--*/

#include "faxui.h"
#include "tapiutil.h"
#include "prtcovpg.h"
#include "tiff.h"
#include "cwabutil.h"
#include  <shellapi.h>

#include  <imm.h>

#ifdef FAX_SCAN_ENABLED

#define TWRESMIN            0
#define TWRESMAX            1
#define TWRESSCAL           2
#define TWRESCUR            3
#define TWRESDEFAULT        4
#define TWRESCLOSEST        5

#define AUTOSCANPREV        0
#define AUTOSCANFINAL       1
#define CUSTOMSCAN          2
#define CANCELLED           3
#define FINALCANCELLED      4
#define AUTOSCANSHEETFED    5
#define CUSTOMDIGITALCAMERA 6
#define DCCANCELLED         7

#define Align(p, x)  (((x) & ((p)-1)) ? (((x) & ~((p)-1)) + p) : (x))

#define WM_PAGE_COMPLETE        (WM_USER+1000)

#define NUM_IFD_ENTRIES         18

#define IFD_SUBFILETYPE         0                // 254
#define IFD_IMAGEWIDTH          1                // 256
#define IFD_IMAGELENGTH         2                // 257
#define IFD_BITSPERSAMPLE       3                // 258
#define IFD_COMPRESSION         4                // 259
#define IFD_PHOTOMETRIC         5                // 262
#define IFD_FILLORDER           6                // 266
#define IFD_STRIPOFFSETS        7                // 273
#define IFD_SAMPLESPERPIXEL     8                // 277
#define IFD_ROWSPERSTRIP        9                // 278
#define IFD_STRIPBYTECOUNTS     10               // 279
#define IFD_XRESOLUTION         11               // 281
#define IFD_YRESOLUTION         12               // 282
#define IFD_GROUP3OPTIONS       13               // 292
#define IFD_RESOLUTIONUNIT      14               // 296
#define IFD_PAGENUMBER          15               // 297
#define IFD_SOFTWARE            16               // 305
#define IFD_CLEANFAXDATA        17               // 327


typedef struct {
    WORD        wIFDEntries;
    TIFF_TAG    ifd[NUM_IFD_ENTRIES];
    DWORD       nextIFDOffset;
    DWORD       filler;
    DWORD       xresNum;
    DWORD       xresDenom;
    DWORD       yresNum;
    DWORD       yresDenom;
    CHAR        software[32];
} FAXIFD, *PFAXIFD;


#define SoftwareStr             "Windows NT Fax Driver"
#define DRIVER_SIGNATURE        'xafD'

FAXIFD FaxIFDTemplate = {

    NUM_IFD_ENTRIES,

    {
        { TIFFTAG_SUBFILETYPE,     TIFF_LONG,     1,                     0                       },
        { TIFFTAG_IMAGEWIDTH,      TIFF_LONG,     1,                     0                       },
        { TIFFTAG_IMAGELENGTH,     TIFF_LONG,     1,                     0                       },
        { TIFFTAG_BITSPERSAMPLE,   TIFF_SHORT,    1,                     1                       },
        { TIFFTAG_COMPRESSION,     TIFF_SHORT,    1,                     COMPRESSION_NONE        },
        { TIFFTAG_PHOTOMETRIC,     TIFF_SHORT,    1,                     PHOTOMETRIC_MINISWHITE  },
        { TIFFTAG_FILLORDER,       TIFF_SHORT,    1,                     FILLORDER_MSB2LSB       },
        { TIFFTAG_STRIPOFFSETS,    TIFF_LONG,     1,                     0                       },
        { TIFFTAG_SAMPLESPERPIXEL, TIFF_SHORT,    1,                     1                       },
        { TIFFTAG_ROWSPERSTRIP,    TIFF_LONG,     1,                     0                       },
        { TIFFTAG_STRIPBYTECOUNTS, TIFF_LONG,     1,                     0                       },
        { TIFFTAG_XRESOLUTION,     TIFF_RATIONAL, 1,                     0                       },
        { TIFFTAG_YRESOLUTION,     TIFF_RATIONAL, 1,                     0                       },
        { TIFFTAG_GROUP3OPTIONS,   TIFF_LONG,     1,                     0                       },
        { TIFFTAG_RESOLUTIONUNIT,  TIFF_SHORT,    1,                     RESUNIT_INCH            },
        { TIFFTAG_PAGENUMBER,      TIFF_SHORT,    2,                     0                       },
        { TIFFTAG_SOFTWARE,        TIFF_ASCII,    sizeof(SoftwareStr)+1, 0                       },
        { TIFFTAG_CLEANFAXDATA,    TIFF_SHORT,    1,                     0                       },
    },

    0,
    DRIVER_SIGNATURE,
    0,
    1,
    0,
    1,
    SoftwareStr
};

#endif



VOID
FillInPropertyPage(
    PROPSHEETPAGE  *psp,
    INT             dlgId,
    DLGPROC         dlgProc,
    PUSERMEM        pUserMem,
    INT             TitleId,
    INT             SubTitleId
    )

/*++

Routine Description:

    Fill out a PROPSHEETPAGE structure with the supplied parameters

Arguments:

    psp - Points to the PROPSHEETPAGE structure to be filled out
    dlgId - Dialog template resource ID
    dlgProc - Dialog procedure
    pUserMem - Pointer to the user mode memory structure
    TitleId - resource id for wizard subtitle
    SubTitleId - resource id for wizard subtitle

Return Value:

    NONE

--*/

{
    LPTSTR WizardTitle;
    LPTSTR WizardSubTitle;

    
    psp->dwSize = sizeof(PROPSHEETPAGE);
    //
    // Don't show titles if it's the first or last page
    //
    if (TitleId==0 && SubTitleId ==0) {
        psp->dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;;
    } else {
        psp->dwFlags = PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
    }
    psp->hInstance = ghInstance;
    psp->pszTemplate = MAKEINTRESOURCE(dlgId);
    psp->pfnDlgProc = dlgProc;
    psp->lParam = (LPARAM) pUserMem;

    WizardTitle    = MemAlloc(200* sizeof(TCHAR) );
    WizardSubTitle = MemAlloc(200* sizeof(TCHAR) );
    LoadString(ghInstance,TitleId,WizardTitle,200);
    LoadString(ghInstance,SubTitleId,WizardSubTitle,200);

    psp->pszHeaderTitle = WizardTitle;
    psp->pszHeaderSubTitle = WizardSubTitle;
}



LPTSTR
GetTextStringValue(
    HWND    hwnd
    )

/*++

Routine Description:

    Retrieve the string value in a text field

Arguments:

    hwnd - Handle to a text window

Return Value:

    Pointer to a string representing the current content of the text field
    NULL if the text field is empty or if there is an error

--*/

{
    INT     length;
    LPTSTR  pString;

    //
    // Find out how many characters are in the text field
    // and allocate enough memory to hold the string value
    //

    if ((length = GetWindowTextLength(hwnd)) == 0 ||
        (pString = MemAlloc(sizeof(TCHAR) * (length + 1))) == NULL)
    {
        return NULL;
    }

    //
    // Actually retrieve the string value
    //

    if (GetWindowText(hwnd, pString, length + 1) == 0) {

        MemFree(pString);
        return NULL;
    }

    return pString;
}



VOID
LimitTextFields(
    HWND    hDlg,
    INT    *pLimitInfo
    )

/*++

Routine Description:

    Limit the maximum length for a number of text fields

Arguments:

    hDlg - Specifies the handle to the dialog window
    pLimitInfo - Array of text field control IDs and their maximum length
        ID for the 1st text field, maximum length for the 1st text field
        ID for the 2nd text field, maximum length for the 2nd text field
        ...
        0
        Note: The maximum length counts the NUL-terminator.

Return Value:

    NONE

--*/

{
    while (*pLimitInfo != 0) {

        SendDlgItemMessage(hDlg, pLimitInfo[0], EM_SETLIMITTEXT, pLimitInfo[1]-1, 0);
        pLimitInfo += 2;
    }
}



VOID
SaveLastRecipientInfo(
    LPTSTR  pName,
    LPTSTR  pAreaCode,
    LPTSTR  pPhoneNumber,
    DWORD   countryId,
    BOOL    useDialingRules
    )

/*++

Routine Description:

    Save the information about the last recipient in the registry

Arguments:

    pName - Specifies the recipient name
    pAreaCode - Specifies the recipient area code
    pPhoneNumber - Specifies the recipient phone number
    countryId - Specifies the recipient country ID
    useDialingRules - Whether use dialing rules is checked

Return Value:

    NONE

--*/

{
    HKEY    hRegKey;

    if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE)) {

        SetRegistryString(hRegKey, REGVAL_LAST_RECNAME, pName);
        SetRegistryString(hRegKey, REGVAL_LAST_RECNUMBER, pPhoneNumber);
        SetRegistryDword(hRegKey, REGVAL_USE_DIALING_RULES, useDialingRules);

        if (useDialingRules) {
            SetRegistryString(hRegKey, REGVAL_LAST_RECAREACODE, pAreaCode);
            SetRegistryDword(hRegKey, REGVAL_LAST_COUNTRYID, countryId);
        }
        RegCloseKey(hRegKey);
    }
}



VOID
RestoreLastRecipientInfo(
    HWND    hDlg,
    PDWORD  pCountryId
    )

/*++

Routine Description:

    Restore the information about the last recipient from the registry

Arguments:

    hDlg - Specifies a handle to the fax recipient wizard page
    pCountryId - Returns the last selected country ID

Return Value:

    NONE

--*/

{
    HKEY    hRegKey;
    LPTSTR  buffer;
    //TCHAR   buffer[MAX_STRING_LEN];

    *pCountryId = GetDefaultCountryID();

    if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READONLY)) {

        buffer = GetRegistryString(hRegKey, REGVAL_LAST_RECNAME, TEXT(""));
        if (buffer) {
            SetDlgItemText(hDlg, IDC_CHOOSE_NAME_EDIT, buffer);
            MemFree(buffer);
        }

        buffer = GetRegistryString(hRegKey, REGVAL_LAST_RECAREACODE, TEXT(""));
        if (buffer) {
            SetDlgItemText(hDlg, IDC_CHOOSE_AREA_CODE_EDIT, buffer);
            MemFree(buffer);
        }

        buffer = GetRegistryString(hRegKey, REGVAL_LAST_RECNUMBER, TEXT(""));
        if (buffer) {
            SetDlgItemText(hDlg, IDC_CHOOSE_NUMBER_EDIT, buffer);
            MemFree(buffer);
        }

        CheckDlgButton(hDlg,
                       IDC_USE_DIALING_RULES,
                       GetRegistryDword(hRegKey, REGVAL_USE_DIALING_RULES));

        *pCountryId = GetRegistryDword(hRegKey, REGVAL_LAST_COUNTRYID);

        RegCloseKey(hRegKey);
    }
}



PUSERMEM
CommonWizardProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam,
    DWORD   buttonFlags
    )

/*++

Routine Description:

    Common procedure for handling wizard pages:

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information
    buttonFlags - Indicate which buttons should be enabled

Return Value:

    NULL - Message is processed and the dialog procedure should return FALSE
    Otherwise - Message is not completely processed and
        The return value is a pointer to the user mode memory structure

--*/

{
    PUSERMEM    pUserMem;

    switch (message) {

    case WM_INITDIALOG:

        //
        // Store the pointer to user mode memory structure
        //

        lParam = ((PROPSHEETPAGE *) lParam)->lParam;
        pUserMem = (PUSERMEM) lParam;
        Assert(ValidPDEVUserMem(pUserMem));
        SetWindowLongPtr(hDlg, DWLP_USER, lParam);
        break;

    case WM_NOTIFY:

        pUserMem = (PUSERMEM) GetWindowLongPtr(hDlg, DWLP_USER);
        Assert(ValidPDEVUserMem(pUserMem));

        switch (((NMHDR *) lParam)->code) {

        case PSN_WIZFINISH:

            pUserMem->finishPressed = TRUE;
            break;

        case PSN_SETACTIVE:

            PropSheet_SetWizButtons(GetParent(hDlg), buttonFlags);
            break;

        case PSN_RESET:
        case PSN_WIZBACK:
        case PSN_WIZNEXT:
        case PSN_KILLACTIVE:
        case LVN_KEYDOWN:

            break;

        default:

            return NULL;
        }

        break;

    case WM_COMMAND:

        pUserMem = (PUSERMEM) GetWindowLongPtr(hDlg, DWLP_USER);
        Assert(ValidPDEVUserMem(pUserMem));
        break;

    default:

        return NULL;
    }

    return pUserMem;
}



/*
 *  IsStringACSII
 *
 *  Purpose: 
 *          This function determines whether a string contains ONLY ASCII characters.
 *
 *  Arguments:
 *          ptszString - points to the string to test.
 *
 *  Returns:
 *          TRUE - indicates that the string contains only ASCII characters.
 *
 */

BOOL IsStringASCII( LPTSTR ptszString )
{
   BOOL     fReturnValue = (BOOL) TRUE;

   // Determine whether the contents of the edit control are legal.

   while ( (*ptszString != (TCHAR) TEXT('\0')) &&
           ( fReturnValue == (BOOL) TRUE) )
   {
      if ( (*ptszString < (TCHAR) 0x0020) || (*ptszString > (TCHAR) MAXCHAR) )
      {
         // The string contains at least one non-ASCII character.

         fReturnValue = (BOOL) FALSE;
      }

      ptszString = _tcsinc( ptszString );
   }   // end of while loop

   return ( fReturnValue );
}



INT
GetCurrentRecipient(
    HWND        hDlg,
    PRECIPIENT *ppRecipient
    )

/*++

Routine Description:

    Extract the current recipient information in the dialog

Arguments:

    hDlg - Handle to the fax recipient wizard page
    ppRecipient - Buffer to receive a pointer to a newly created RECIPIENT structure
        NULL if caller is only interested in the validity of recipient info

Return Value:

    = 0 if successful
    > 0 error message string resource ID otherwise
    < 0 other error conditions

--*/

{
    LPLINECOUNTRYENTRY  pLineCountryEntry;
    DWORD               countryId, countryCode;
    PRECIPIENT          pRecipient;
    TCHAR               areaCode[MAX_RECIPIENT_NUMBER];
    TCHAR               phoneNumber[MAX_RECIPIENT_NUMBER];
    INT                 nameLen, areaCodeLen, numberLen;
    LPTSTR              pName, pAddress;
    INT                 useDialingRules;

    //
    // Default value in case of error
    //

    if (ppRecipient)
        *ppRecipient = NULL;

    //
    // Find the current country code
    //

    countryCode = 0;
    pLineCountryEntry = NULL;
    countryId = GetCountryListBoxSel(GetDlgItem(hDlg, IDC_CHOOSE_COUNTRY_COMBO));
    useDialingRules = IsDlgButtonChecked(hDlg, IDC_USE_DIALING_RULES);

    if ((useDialingRules == BST_CHECKED) &&
        (pLineCountryEntry = FindCountry(countryId)))
    {
        countryCode = pLineCountryEntry->dwCountryCode;
    }

    nameLen = GetWindowTextLength(GetDlgItem(hDlg, IDC_CHOOSE_NAME_EDIT));
    areaCodeLen = GetWindowTextLength(GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT));
    numberLen = GetWindowTextLength(GetDlgItem(hDlg, IDC_CHOOSE_NUMBER_EDIT));

    //
    // Validate the edit text fields
    //

    if (nameLen <= 0)
        return IDS_BAD_RECIPIENT_NAME;

    if (numberLen <= 0 || numberLen >= MAX_RECIPIENT_NUMBER)
        return IDS_BAD_RECIPIENT_NUMBER;

    if ((areaCodeLen <= 0 && AreaCodeRules(pLineCountryEntry) == AREACODE_REQUIRED) ||
        (areaCodeLen >= MAX_RECIPIENT_NUMBER))
    {
        return IDS_BAD_RECIPIENT_AREACODE;
    }

    if (ppRecipient == NULL)
        return 0;

    //
    // Calculate the amount of memory space we need and allocate it
    //

    pRecipient = MemAllocZ(sizeof(RECIPIENT));
    pName = MemAllocZ((nameLen + 1) * sizeof(TCHAR));
    pAddress = MemAllocZ((areaCodeLen + numberLen + 20) * sizeof(TCHAR));

    if (!pRecipient || !pName || !pAddress) {

        MemFree(pRecipient);
        MemFree(pName);
        MemFree(pAddress);
        return -1;
    }

    *ppRecipient = pRecipient;
    pRecipient->pName = pName;
    pRecipient->pAddress = pAddress;

    //
    // Get the recipient's name
    //

    GetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_NAME_EDIT), pName, nameLen+1);

    //
    // Get the recipient's number
    //  AddressType
    //  [+ CountryCode Space]
    //  [( AreaCode ) Space]
    //  SubscriberNumber
    //

    GetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT), areaCode, MAX_RECIPIENT_NUMBER);

    if ( IsStringASCII( areaCode ) == (BOOL) FALSE )
    {
       return ( (INT) IDS_ERROR_AREA_CODE );
    }
    
    GetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_NUMBER_EDIT), phoneNumber, MAX_RECIPIENT_NUMBER);

    if ( IsStringASCII( phoneNumber ) == (BOOL) FALSE )
    {
       return ( (INT) IDS_ERROR_FAX_NUMBER );
    }

    AssemblePhoneNumber(pAddress,
                        countryCode,
                        areaCode,
                        phoneNumber);

    //
    // Save the information about the last recipient as a convenience
    //

    SaveLastRecipientInfo(pName,
                          areaCode,
                          phoneNumber,
                          countryId,
                          useDialingRules == BST_CHECKED);

    return 0;
}



VOID
InitRecipientListView(
    HWND    hwndLV
    )

/*++

Routine Description:

    Initialize the recipient list view on the first page of Send Fax wizard

Arguments:

    hwndLV - Window handle to the list view control

Return Value:

    NONE

--*/

{
    LV_COLUMN   lvc;
    RECT        rect;
    TCHAR       buffer[MAX_TITLE_LEN];

    if (hwndLV == NULL)
        return;

    GetClientRect(hwndLV, &rect);

    ZeroMemory(&lvc, sizeof(lvc));

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    lvc.fmt = LVCFMT_LEFT;
    lvc.pszText = buffer;
    lvc.cx = (rect.right - rect.left) / 2;

    lvc.iSubItem = 0;
    LoadString(ghInstance, IDS_COLUMN_RECIPIENT_NAME, buffer, MAX_TITLE_LEN);

    if (ListView_InsertColumn(hwndLV, 0, &lvc) == -1)
        Error(("ListView_InsertColumn failed\n"));

    lvc.cx -= GetSystemMetrics(SM_CXVSCROLL);
    lvc.iSubItem = 1;
    LoadString(ghInstance, IDS_COLUMN_RECIPIENT_NUMBER, buffer, MAX_TITLE_LEN);

    if (ListView_InsertColumn(hwndLV, 1, &lvc) == -1)
        Error(("ListView_InsertColumn failed\n"));
}



BOOL
InsertRecipientListItem(
    HWND        hwndLV,
    PRECIPIENT  pRecipient
    )

/*++

Routine Description:

    Insert an item into the recipient list view

Arguments:

    hwndLV - Window handle to the recipient list view
    pRecipient - Specifies the recipient to be inserted

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    LV_ITEM lvi;
    INT     index;

    ZeroMemory(&lvi, sizeof(lvi));

    lvi.mask = LVIF_PARAM | LVIF_TEXT | LVIF_STATE;
    lvi.lParam = (LPARAM) pRecipient;
    lvi.pszText = pRecipient->pName;
    lvi.state = lvi.stateMask = LVIS_SELECTED;

    if ((index = ListView_InsertItem(hwndLV, &lvi)) == -1) {

        Error(("ListView_InsertItem failed\n"));
        return FALSE;
    }

    lvi.mask = LVIF_TEXT;
    lvi.iItem = index;
    lvi.iSubItem = 1;
    lvi.pszText = pRecipient->pAddress;

    if (! ListView_SetItem(hwndLV, &lvi))
        Error(("ListView_SetItem failed\n"));

    return TRUE;
}



PRECIPIENT
GetRecipientListItem(
    HWND    hwndLV,
    INT     index
    )

/*++

Routine Description:

    Retrieve the recipient associated with an item in the list view

Arguments:

    hwndLV - Window handle to the recipient list view
    index - Specifies the index of the interested item

Return Value:

    Pointer to the requested recipient information
    NULL if there is an error

--*/

{
    LV_ITEM lvi;

    ZeroMemory(&lvi, sizeof(lvi));
    lvi.mask = LVIF_PARAM;
    lvi.iItem = index;

    if (ListView_GetItem(hwndLV, &lvi))
        return (PRECIPIENT) lvi.lParam;

    Error(("ListView_GetItem failed\n"));
    return NULL;
}



INT
AddRecipient(
    HWND        hDlg,
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Add the current recipient information entered by the user
    into the recipient list

Arguments:

    hDlg - Handle to the fax recipient wizard page
    pUserMem - Points to user mode memory structure

Return Value:

    Same meaning as return value from GetCurrentRecipient, i.e.
    = 0 if successful
    > 0 error message string resource ID otherwise
    < 0 other error conditions

--*/

{
    PRECIPIENT  pRecipient;
    INT         errId;
    HWND        hwndLV;

    //
    // Collect information about the current recipient
    //

    if ((errId = GetCurrentRecipient(hDlg, &pRecipient)) != 0)
        return errId;

    //
    // Insert the current recipient to the recipient list
    //

    if ((hwndLV = GetDlgItem(hDlg, IDC_CHOOSE_RECIPIENT_LIST)) &&
        InsertRecipientListItem(hwndLV, pRecipient))
    {
        errId = 0;
        pRecipient->pNext = pUserMem->pRecipients;
        pUserMem->pRecipients = pRecipient;

        //
        // Clear the name and number fields after successfully
        // adding the recipient to the internal list
        //

        SetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_NAME_EDIT), TEXT(""));
        SetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT), TEXT(""));
        SetWindowText(GetDlgItem(hDlg, IDC_CHOOSE_NUMBER_EDIT), TEXT(""));

        //CheckDlgButton(hDlg, IDC_USE_DIALING_RULES, FALSE);

    } else {

        FreeRecipient(pRecipient);
        errId = -1;
    }

    return errId;
}



BOOL
DoAddressBook(
    HWND        hDlg,
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Display the MAPI address book dialog

Arguments:

    hDlg - Handle to the fax recipient wizard page
    pUserMem - Points to user mode memory structure

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    HWND            hwndLV;
    BOOL            result;
    PRECIPIENT      pNewRecip = NULL;
    PRECIPIENT      tmpRecip;

    if (! pUserMem->lpWabInit && 
        ! (pUserMem->lpWabInit = InitializeWAB(ghInstance))) 
    {
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_ADDRBOOK), FALSE);
        return FALSE;
    }

    //
    // Get a handle to the recipient list window
    //

    if (! (hwndLV = GetDlgItem(hDlg, IDC_CHOOSE_RECIPIENT_LIST)))
        return FALSE;

    //
    // Add current recipient to the list if necessary
    //

    AddRecipient(hDlg, pUserMem);

    result = CallWabAddress(
                hDlg,
                pUserMem,
                &pNewRecip
                );
                
    FreeRecipientList(pUserMem);
    
    pUserMem->pRecipients = pNewRecip;

    ListView_DeleteAllItems(hwndLV);

    for (tmpRecip = pNewRecip; tmpRecip; tmpRecip = tmpRecip->pNext) {
        InsertRecipientListItem(hwndLV, tmpRecip);
    }

    if (!result) {
        DisplayMessageDialog( hDlg, MB_OK, 0, IDS_BAD_ADDRESS_TYPE );
    }
    
    return result;
}



BOOL
ValidateRecipients(
    HWND        hDlg,
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Validate the list of fax recipients entered by the user

Arguments:

    hDlg - Handle to the fax recipient wizard page
    pUserMem - Points to user mode memory structure

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    INT errId;

    //
    // Add current recipient to the list if necessary
    //

    errId = AddRecipient(hDlg, pUserMem);

    //
    // There must be at least one recipient
    //

    if (pUserMem->pRecipients)
        return TRUE;

    //
    // Display an error message
    //

    if (errId > 0)
        DisplayMessageDialog(hDlg, 0, 0, errId);
    else
        MessageBeep(MB_OK);

    //
    // Set current focus to the appropriate text field as a convenience
    //

    switch (errId) {

    case IDS_ERROR_FAX_NUMBER:
        SetDlgItemText(hDlg, IDC_CHOOSE_NUMBER_EDIT, L"");
    case IDS_BAD_RECIPIENT_NUMBER:    

        errId = IDC_CHOOSE_NUMBER_EDIT;
        break;

    case IDS_ERROR_AREA_CODE:
        SetDlgItemText(hDlg, IDC_CHOOSE_AREA_CODE_EDIT, L"");
    case IDS_BAD_RECIPIENT_AREACODE:

        errId = IDC_CHOOSE_AREA_CODE_EDIT;
        break;

    case IDS_BAD_RECIPIENT_NAME:
    default:

        errId = IDC_CHOOSE_NAME_EDIT;
        break;
    }

    SetFocus(GetDlgItem(hDlg, errId));
    return FALSE;
}



PRECIPIENT *
FindRecipient(
    PUSERMEM    pUserMem,
    PRECIPIENT  pRecipient
    )

/*++

Routine Description:

    Check if the specified recipient is in the list of recipients

Arguments:

    pUserMem - Points to user mode memory structure
    pRecipient - Specifies the recipient to be found

Return Value:

    Address of the link pointer to the specified recipient
    NULL if the specified recipient is not found

--*/

{
    PRECIPIENT  pCurrent, *ppPrevNext;

    //
    // Search for the specified recipient in the list
    //

    ppPrevNext = (PRECIPIENT *) &pUserMem->pRecipients;
    pCurrent = pUserMem->pRecipients;

    while (pCurrent && pCurrent != pRecipient) {

        ppPrevNext = (PRECIPIENT *) &pCurrent->pNext;
        pCurrent = pCurrent->pNext;
    }

    //
    // Return the address of the link pointer to the specified recipient
    // or NULL if the specified recipient is not found
    //

    return pCurrent ? ppPrevNext : NULL;
}



BOOL
RemoveRecipient(
    HWND        hDlg,
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Remove the currently selected recipient from the recipient list

Arguments:

    hDlg - Handle to the fax recipient wizard page
    pUserMem - Points to user mode memory structure

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    PRECIPIENT  pRecipient, *ppPrevNext;
    INT         selIndex;
    HWND        hwndLV;

    //
    // Get the currently selected recipient, and
    // Find the current recipient in the list, then
    // Delete the current recipient and select the next one below it
    //

    if ((hwndLV = GetDlgItem(hDlg, IDC_CHOOSE_RECIPIENT_LIST)) &&
        (selIndex = ListView_GetNextItem(hwndLV, -1, LVNI_ALL|LVNI_SELECTED)) != -1 &&
        (pRecipient = GetRecipientListItem(hwndLV, selIndex)) &&
        (ppPrevNext = FindRecipient(pUserMem, pRecipient)) &&
        ListView_DeleteItem(hwndLV, selIndex))
    {
        ListView_SetItemState(hwndLV,
                              selIndex,
                              LVIS_SELECTED|LVIS_FOCUSED,
                              LVIS_SELECTED|LVIS_FOCUSED);

        //
        // Delete the recipient from the internal list
        //

        *ppPrevNext = pRecipient->pNext;
        FreeRecipient(pRecipient);

        return TRUE;
    }

    MessageBeep(MB_ICONHAND);
    return FALSE;
}



VOID
LocationListInit(
    HWND        hDlg,
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Initialize the list of TAPI locations

Arguments:

    hDlg - Handle to "Compose New Fax" wizard window
    pUserMem - Pointer to user mode memory structure

Return Value:

    NONE

--*/

{
    HWND    hwndList;
    DWORD   index;
    LRESULT listIdx;
    LPTSTR  pLocationName, pSelectedName = NULL;
    LPLINETRANSLATECAPS pTranslateCaps = NULL;
    LPLINELOCATIONENTRY pLocationEntry;

    //
    // For remote printers, hide the location combo-box
    //

    if (! pUserMem->isLocalPrinter) {

        ShowWindow(GetDlgItem(hDlg, IDC_LOCATION_PROMPT), SW_HIDE);
        ShowWindow(GetDlgItem(hDlg, IDC_LOCATION_LIST), SW_HIDE);
        ShowWindow(GetDlgItem(hDlg, IDC_TAPI_PROPS), SW_HIDE);
        return;
    }

    //
    // Get the list of locations from TAPI and use it
    // to initialize the location combo-box.
    //

    if ((hwndList = GetDlgItem(hDlg, IDC_LOCATION_LIST)) &&
        (pTranslateCaps = GetTapiLocationInfo(hDlg)))
    {
        SendMessage(hwndList, CB_RESETCONTENT, 0, 0);

        pLocationEntry = (LPLINELOCATIONENTRY)
            ((PBYTE) pTranslateCaps + pTranslateCaps->dwLocationListOffset);

        for (index=0; index < pTranslateCaps->dwNumLocations; index++) {

            pLocationName = (LPTSTR)
                ((PBYTE) pTranslateCaps + pLocationEntry->dwLocationNameOffset);

            if (pLocationEntry->dwPermanentLocationID == pTranslateCaps->dwCurrentLocationID)
                pSelectedName = pLocationName;

            listIdx = SendMessage(hwndList, CB_INSERTSTRING, 0, (LPARAM) pLocationName);

            if (listIdx != CB_ERR) {

                SendMessage(hwndList,
                            CB_SETITEMDATA,
                            listIdx,
                            pLocationEntry->dwPermanentLocationID);
            }

            pLocationEntry++;
        }

        if (pSelectedName != NULL)
            SendMessage(hwndList, CB_SELECTSTRING, (WPARAM) -1, (LPARAM) pSelectedName);
    }

    MemFree(pTranslateCaps);
}



VOID
LocationListSelChange(
    HWND    hDlg
    )

/*++

Routine Description:

    Change the default TAPI location

Arguments:

    hDlg - Handle to "Compose New Fax" wizard window

Return Value:

    NONE

--*/

{
    HWND    hwndList;
    LRESULT selIndex;
    DWORD locationID;

    if ((hwndList = GetDlgItem(hDlg, IDC_LOCATION_LIST)) &&
        (selIndex = SendMessage(hwndList, CB_GETCURSEL, 0, 0)) != CB_ERR &&
        (locationID = (DWORD)SendMessage(hwndList, CB_GETITEMDATA, selIndex, 0)) != CB_ERR)
    {
        SetCurrentLocation(locationID);
    }
}



VOID
UpdateCountryCombobox(
    HWND    hDlg,
    DWORD   countryId
    )

/*++

Routine Description:

    Update the country/region combobox

Arguments:

    hDlg - Handle to recipient wizard page
    countryId -  country id

Return Value:

    NONE

--*/

{
    HWND hwndUseDialingRultes = GetDlgItem(hDlg, IDC_USE_DIALING_RULES);
    HWND hwndDialingLocation = GetDlgItem(hDlg, IDC_LOCATION_LIST);
    HWND hwndDialingLocationStatic = GetDlgItem(hDlg, IDC_LOCATION_PROMPT);
    HWND hwndLocation = GetDlgItem(hDlg, IDC_CHOOSE_COUNTRY_COMBO);
    HWND hwndLocationStatic = GetDlgItem(hDlg, IDC_STATIC_CHOOSE_COUNTRY_COMBO);
    HWND hwndProp = GetDlgItem(hDlg, IDC_TAPI_PROPS);
    static int OldCountryCode = 0;

    if (IsDlgButtonChecked(hDlg, IDC_USE_DIALING_RULES) != BST_CHECKED) {
        //
        // user unchecked use dialing rules
        // remember the old country code and area code and clear out the UI
        //
        OldCountryCode = GetCountryListBoxSel(hwndLocation);
        if (OldCountryCode == -1) {
            OldCountryCode = countryId != -1 ? countryId : 0;
        }
        SendMessage(hwndLocation, CB_RESETCONTENT, FALSE, 0);
        EnableWindow(hwndLocation, FALSE);
        EnableWindow(hwndLocationStatic, FALSE);
        EnableWindow(hwndDialingLocation, FALSE);
        EnableWindow(hwndDialingLocationStatic, FALSE);
        EnableWindow(hwndProp, FALSE);
    }
    else {
        //
        // user checked use dialing rules
        // enable country combo, restore settings
        //
        EnableWindow(hwndLocation, TRUE);
        EnableWindow(hwndLocationStatic, TRUE);
        InitCountryListBox(GetDlgItem(hDlg, IDC_CHOOSE_COUNTRY_COMBO),
                           GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT),
                           countryId != -1 ? countryId : OldCountryCode);
        EnableWindow(hwndDialingLocation, TRUE);
        EnableWindow(hwndDialingLocationStatic, TRUE);
        EnableWindow(hwndProp, TRUE);
    }    

    SelChangeCountryListBox(GetDlgItem(hDlg, IDC_CHOOSE_COUNTRY_COMBO),
                            GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT));
}

INT_PTR
CALLBACK
firstdlgproc(
    HWND hDlg,
    UINT iMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch(iMsg) {
        case WM_INITDIALOG:
            CheckDlgButton(hDlg, IDC_EDIT_USERINFO_NOW, TRUE);
            return TRUE;

        case WM_COMMAND:
            switch(LOWORD( wParam )) {
                case IDOK:
                    if (IsDlgButtonChecked(hDlg, IDC_EDIT_USERINFO_NOW) == BST_CHECKED) {
                        EndDialog( hDlg, IDYES );
                    }
                    else {
                        EndDialog( hDlg, IDNO );
                    }
                    return TRUE;
            }
            break;
    }

    return FALSE;
}

INT_PTR
CALLBACK
firstprintdlgproc(
    HWND hDlg,
    UINT iMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch(iMsg) {
        
        case WM_COMMAND:
            EndDialog( hDlg, LOWORD( wParam ));
            break;
    }

    return FALSE;
}


BOOL
DoFirstTimeInitStuff(
    HWND hDlg,
    BOOL bWelcomePage
    )
{
   BOOL bInitialized = TRUE;
   HKEY hRegKey;
 //  TCHAR MessageBuffer[1024],TitleBuffer[100];
   SHELLEXECUTEINFO shellExeInfo = {
        sizeof(SHELLEXECUTEINFO),
        SEE_MASK_NOCLOSEPROCESS,
        hDlg,
        L"Open",
        L"rundll32",
        L"shell32.dll,Control_RunDLL fax.cpl",
        NULL,
        SW_SHOWNORMAL,
    };

    TCHAR ScratchCmdLine[MAX_PATH];
    PCTSTR PrinterCmdLine = TEXT("rundll32.exe printui.dll,PrintUIEntry /il"); 
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
   
   //
   // print or fax first time?
   //
   if (bWelcomePage && !GetEnvironmentVariable(TEXT("NTFaxSendNote"), NULL, 0)) {
       DWORD PrinterCount = 1;
       PRINTER_INFO_4 *pPrinterInfo4;

       if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READONLY)) {

           bInitialized = GetRegistryDword(hRegKey, REGVAL_PRINTER_INITIALIZED);
           RegCloseKey(hRegKey);

       }

       if (bInitialized) {
           return TRUE;      
       }


       //
       // if there is more than one printer don't confuse the user by asking
       // to add a printer since one is already installed.
       //
       pPrinterInfo4 = MyEnumPrinters(
                        NULL, 
                        4,
                        &PrinterCount,
                        PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS);
           
       if (pPrinterInfo4) {
           MemFree(pPrinterInfo4);
           if (PrinterCount > 1) {
               if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE)) {

                   SetRegistryDword(hRegKey, REGVAL_PRINTER_INITIALIZED , 1);
                   RegCloseKey(hRegKey);

               }

               return(TRUE);
           }
       }
       
   
       if (DialogBoxParam(ghInstance,
                     MAKEINTRESOURCE( IDD_WIZFIRSTTIMEPRINT ),
                     hDlg,
                     firstprintdlgproc,
                     (LPARAM)NULL) != IDOK) {
           //
           // if they said "print", then launch the add/remove printer wizard
           //
           ZeroMemory(&si,sizeof(si));
           GetStartupInfo(&si);
           lstrcpy(ScratchCmdLine,PrinterCmdLine);
           if (CreateProcess(
                        NULL,
                        ScratchCmdLine,
                        NULL,
                        NULL,
                        FALSE,
                        DETACHED_PROCESS,
                        NULL,
                        NULL,
                        &si,
                        &pi
                        )) {
               CloseHandle( pi.hThread );
               CloseHandle( pi.hProcess );
           }
           
           return(FALSE);
       } else {
           if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE)) {

               SetRegistryDword(hRegKey, REGVAL_PRINTER_INITIALIZED , 1);
               RegCloseKey(hRegKey);

           }

           return(TRUE);
       }

   } else if (!bWelcomePage) {
   
       if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READONLY)) {

           bInitialized = GetRegistryDword(hRegKey, REGVAL_WIZARD_INITIALIZED);
           RegCloseKey(hRegKey);

       }

       if (bInitialized) {
           return TRUE;      
       }


       //
       // show the user a dialog
       //
       if (DialogBoxParam(ghInstance,
                          MAKEINTRESOURCE( IDD_WIZFIRSTTIME ),
                          hDlg,
                          firstdlgproc,
                          (LPARAM)NULL) == IDYES) {
           //
           // if they said yes, then launch the control panel applet
           //
           if (!ShellExecuteEx(&shellExeInfo)) {
              DisplayMessageDialog(hDlg, 0, 0, IDS_ERR_CPL_LAUNCH);
              return FALSE;
           }
    
           WaitForSingleObject( shellExeInfo.hProcess, INFINITE );
           CloseHandle( shellExeInfo.hProcess ) ;
    
       }
           
       //
       // set the reg key so this doesn't come up again
       //
       if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE)) {
    
           SetRegistryDword(hRegKey, REGVAL_WIZARD_INITIALIZED , 1 );
           RegCloseKey(hRegKey);
    
       }

   }
   
   return(TRUE);

}

INT_PTR
RecipientWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the first wizard page: selecting the fax recipient

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

#define UpdateAddToListButton() \
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_ADD), GetCurrentRecipient(hDlg, NULL) == 0)

#define UpdateRemoveFromListButton(__BoolFlag) \
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_REMOVE),__BoolFlag)

{
    PUSERMEM    pUserMem;
    DWORD       countryId;
    INT         cmd;
    NMHDR      *pNMHdr;
    
    DWORD  dwErrorCode;

    HANDLE hEditControl;

    TCHAR  tszBuffer[MAX_STRING_LEN];

    //
    // Maximum length for various text fields
    //

    static INT  textLimits[] = {

        IDC_CHOOSE_NAME_EDIT,       64,
        IDC_CHOOSE_AREA_CODE_EDIT,  11,
        IDC_CHOOSE_NUMBER_EDIT,     51,
        0
    };

    //
    // Handle common messages shared by all wizard pages
    //

    if (! (pUserMem = CommonWizardProc(hDlg, message, wParam, lParam, PSWIZB_BACK | PSWIZB_NEXT)))
        return FALSE;

    switch (message) {

    case WM_INITDIALOG:

        //
        // check if the user has run the wizard before so they can fill in the coverpage info.
        //
        DoFirstTimeInitStuff(hDlg, FALSE);                

        //
        // tapi is asynchronously initialized, wait for it to finish spinning up.
        //
        WaitForSingleObject( pUserMem->hTapiEvent, INFINITE );

        //
        // Restore the last recipient information as a convenience
        //        

        RestoreLastRecipientInfo(hDlg, &countryId);

        //
        // Initialize the list of countries
        //

        UpdateCountryCombobox(hDlg, countryId);

        //
        // Check if MAPI is installed - we need it for address book features
        //

//        if (! IsMapiAvailable())
//            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_ADDRBOOK), FALSE);

        LimitTextFields(hDlg, textLimits);

        //
        // Initialize the recipient list view
        //

        InitRecipientListView(GetDlgItem(hDlg, IDC_CHOOSE_RECIPIENT_LIST));

        //
        // Initialize the location combo-box
        //

        LocationListInit(hDlg, pUserMem);

        // Disable the IME for the area code edit control.
    
        hEditControl = GetDlgItem( hDlg, IDC_CHOOSE_AREA_CODE_EDIT );
    
        if ( hEditControl != (HWND) INVALID_HANDLE_VALUE )
        {
           ImmAssociateContext( hEditControl, (HIMC) NULL );
        }

        // Disable the IME for the fax phone number edit control.
    
        hEditControl = GetDlgItem( hDlg, IDC_CHOOSE_NUMBER_EDIT );
    
        if ( hEditControl != (HWND) INVALID_HANDLE_VALUE )
        {
           ImmAssociateContext( hEditControl, (HIMC) NULL );
        }

        break;

    case WM_NOTIFY:

        pNMHdr = (NMHDR *) lParam;

        switch (pNMHdr->code) {

        case LVN_KEYDOWN:

            if (pNMHdr->hwndFrom == GetDlgItem(hDlg, IDC_CHOOSE_RECIPIENT_LIST) &&
                ((LV_KEYDOWN *) pNMHdr)->wVKey == VK_DELETE)
            {
                RemoveRecipient(hDlg, pUserMem);                
            }
            break;

        case PSN_WIZNEXT:

            if (! ValidateRecipients(hDlg, pUserMem)) {

                //
                // Validate the list of recipients and prevent the user
                // from advancing to the next page if there is a problem
                //

                SetWindowLongPtr(hDlg, DWLP_MSGRESULT, -1);
                return TRUE;
            }
            break;

        case PSN_SETACTIVE:
            //
            // make sure the remove button has the correct state
            //
            UpdateRemoveFromListButton(pUserMem->pRecipients ? TRUE : FALSE);
            break;
        }

        return FALSE;

    case WM_COMMAND:

        cmd = GET_WM_COMMAND_CMD(wParam, lParam);

        switch (GET_WM_COMMAND_ID(wParam, lParam)) {

        case IDC_CHOOSE_COUNTRY_COMBO:

            if (cmd == CBN_SELCHANGE) {

                //
                // Update the area code edit box if necessary
                //

                SelChangeCountryListBox(GetDlgItem(hDlg, IDC_CHOOSE_COUNTRY_COMBO),
                                        GetDlgItem(hDlg, IDC_CHOOSE_AREA_CODE_EDIT));

                UpdateAddToListButton();
                
            }
            break;

        case IDC_CHOOSE_NAME_EDIT:

            if (cmd == EN_CHANGE)
            {
               UpdateAddToListButton();
               
            }

            break;

        case IDC_CHOOSE_AREA_CODE_EDIT:

            if (cmd == EN_CHANGE)
            {
               UpdateAddToListButton();
               
               // Look for DBCS in the edit control.
 
               // Read the text from the edit control.
            
               if ( GetDlgItemText( hDlg, IDC_CHOOSE_AREA_CODE_EDIT, tszBuffer,
                    MAX_STRING_LEN ) != 0 )
               {
                  // Determine whether the contents of the edit control are legal.

                  if ( IsStringASCII( tszBuffer ) != (BOOL) TRUE )
                  {
                     LoadString(ghInstance, IDS_ERROR_AREA_CODE,
                                tszBuffer, MAX_STRING_LEN);

                     MessageBox( hDlg, tszBuffer, NULL,
                                 (UINT) (MB_ICONSTOP | MB_OK) );

                     SetDlgItemText(hDlg, IDC_CHOOSE_AREA_CODE_EDIT, L"");
                  }                  

               }
               else
               {
                  dwErrorCode = GetLastError();
            
                  if ( dwErrorCode != (DWORD) ERROR_SUCCESS )
                  {
                     // Error reading the edit control.
                  }
               }
            }

            break;

        case IDC_CHOOSE_NUMBER_EDIT:

            if (cmd == EN_CHANGE)
            {
               UpdateAddToListButton();

               // Look for DBCS in the edit control.
 
               // Read the text from the edit control.
            
               if ( GetDlgItemText( hDlg, IDC_CHOOSE_NUMBER_EDIT, tszBuffer,
                    MAX_STRING_LEN ) != 0 )
               {
                  // Determine whether the contents of the edit control are legal.

                  if ( IsStringASCII( tszBuffer ) != (BOOL) TRUE )
                  {
                     LoadString(ghInstance, IDS_ERROR_FAX_NUMBER,
                                tszBuffer, MAX_STRING_LEN);

                     MessageBox( hDlg, tszBuffer, NULL,
                                 (UINT) (MB_ICONSTOP | MB_OK) );

                     SetDlgItemText(hDlg, IDC_CHOOSE_NUMBER_EDIT, L"");
                  }
               }
               else
               {
                  dwErrorCode = GetLastError();
            
                  if ( dwErrorCode != (DWORD) ERROR_SUCCESS )
                  {
                     // Error reading the edit control.
                  }
               }
            }

            break;

        case IDC_USE_DIALING_RULES:

            UpdateCountryCombobox(hDlg, -1);
            UpdateAddToListButton();            

            break;

        case IDC_CHOOSE_ADDRBOOK:

            DoAddressBook(hDlg, pUserMem);
            UpdateRemoveFromListButton(pUserMem->pRecipients ? TRUE : FALSE);
            break;

        case IDC_TAPI_PROPS:

            DoTapiProps(hDlg);            
            LocationListInit(hDlg, pUserMem);
            break;

        case IDC_LOCATION_LIST:

            if (cmd == CBN_SELCHANGE)
                LocationListSelChange(hDlg);
            break;

        case IDC_CHOOSE_ADD:

            if ((cmd = AddRecipient(hDlg, pUserMem)) != 0) {

                if (cmd > 0)
                    DisplayMessageDialog(hDlg, 0, 0, cmd);
                else
                    MessageBeep(MB_OK);

            } else {            
                SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_NAME_EDIT));
                //
                // enable the remove button
                //
                UpdateRemoveFromListButton(TRUE);
            }
            break;
         case IDC_CHOOSE_REMOVE:
            RemoveRecipient(hDlg, pUserMem);
            //
            // disable the remove button if there are no more recipients
            //
            if (!pUserMem->pRecipients) {
                UpdateRemoveFromListButton(FALSE);
            }
        }
        break;
    }

    return TRUE;
}



VOID
ValidateSelectedCoverPage(
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    If a cover page is selected, then do the following:
        if the cover page file is a link resolve it
        check if the cover page file contains note/subject fields

Arguments:

    pUserMem - Points to user mode memory structure

Return Value:

    NONE

--*/

{
    TCHAR       filename[MAX_PATH];
    COVDOCINFO  covDocInfo;
    DWORD       ec;

    if (ResolveShortcut(pUserMem->coverPage, filename))
        _tcscpy(pUserMem->coverPage, filename);

    Verbose(("Cover page selected: %ws\n", pUserMem->coverPage));

    ec = PrintCoverPage(NULL, NULL, pUserMem->coverPage, &covDocInfo);
    if (!ec) {

        pUserMem->noteSubjectFlag = covDocInfo.Flags;
        pUserMem->cpPaperSize = covDocInfo.PaperSize;
        pUserMem->cpOrientation = covDocInfo.Orientation;

    } else {

        Error(("Cannot examine cover page file '%ws': %d\n",
               pUserMem->coverPage,
               ec));
    }
}



INT_PTR
CoverPageWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the second wizard page:
    selecting cover page and setting other fax options

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

{
    static INT  textLimits[] = {

        IDC_CHOOSE_CP_SUBJECT,   256,
        IDC_CHOOSE_CP_NOTE,   8192,
        0
    };

    PUSERMEM    pUserMem;
    PDMPRIVATE  pdmPrivate;
    WORD        cmdId;
    HKEY        hRegKey;



    //
    // Handle common messages shared by all wizard pages
    //

    if (! (pUserMem = CommonWizardProc(hDlg, message, wParam, lParam, PSWIZB_BACK|PSWIZB_NEXT)))
        return FALSE;

    //
    // Handle anything specific to the current wizard page
    //

    pdmPrivate = &pUserMem->devmode.dmPrivate;

    switch (message) {

    case WM_INITDIALOG:

        //
        // Retrieve the most recently used cover page settings
        //

        if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READONLY)) {
            LPTSTR tmp;
            pdmPrivate->sendCoverPage =
                GetRegistryDword(hRegKey, REGVAL_SEND_COVERPG);

                tmp = GetRegistryString(hRegKey, REGVAL_COVERPG, TEXT("") );
                if (tmp) {
                    lstrcpy(pUserMem->coverPage, tmp );
                    MemFree(tmp);
                }
            RegCloseKey(hRegKey);
        }

        //
        // Initialize the list of cover pages
        //

        WaitForSingleObject( pUserMem->hFaxSvcEvent, INFINITE );
        if (pUserMem->pCPInfo = AllocCoverPageInfo(pUserMem->hPrinter, pUserMem->ServerCPOnly)) {

            InitCoverPageList(pUserMem->pCPInfo,
                              GetDlgItem(hDlg, IDC_CHOOSE_CP_LIST),
                              pUserMem->coverPage);
        }

        //
        // Indicate whether cover page should be sent
        //

        if (SendDlgItemMessage(hDlg, IDC_CHOOSE_CP_LIST, CB_GETCOUNT, 0, 0) <= 0) {
            pdmPrivate->sendCoverPage = FALSE;
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_CHECK), FALSE);
        }

        CheckDlgButton(hDlg, IDC_CHOOSE_CP_CHECK, pdmPrivate->sendCoverPage);
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_LIST), pdmPrivate->sendCoverPage);
        EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_SUBJECT), pdmPrivate->sendCoverPage);
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_SUBJECT), pdmPrivate->sendCoverPage);
        EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_NOTE), pdmPrivate->sendCoverPage);
        EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_NOTE), pdmPrivate->sendCoverPage);

        //
        // make sure the user selects a coverpage if this is the fax send utility
        //
        if (GetEnvironmentVariable(TEXT("NTFaxSendNote"), NULL, 0)) {
            pdmPrivate->sendCoverPage = TRUE;
            CheckDlgButton(hDlg, IDC_CHOOSE_CP_CHECK, TRUE);
            // hide the checkbox
            MyHideWindow(GetDlgItem(hDlg,IDC_CHOOSE_CP_CHECK) );
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_LIST), TRUE);
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_SUBJECT), TRUE);
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_SUBJECT), TRUE);
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_NOTE), TRUE);
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_NOTE), TRUE);
        } else {
           MyHideWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_NOCHECK) );
        }

        LimitTextFields(hDlg, textLimits);
                                                         
        break;

    case WM_COMMAND:
        switch (cmdId = GET_WM_COMMAND_ID(wParam, lParam)) {

        case IDC_CHOOSE_CP_CHECK:
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP), IsDlgButtonChecked(hDlg, cmdId) );
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_LIST), IsDlgButtonChecked(hDlg, cmdId) );
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_SUBJECT), IsDlgButtonChecked(hDlg, cmdId) );
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_SUBJECT), IsDlgButtonChecked(hDlg, cmdId) );
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_CHOOSE_CP_NOTE), IsDlgButtonChecked(hDlg, cmdId) );
            EnableWindow(GetDlgItem(hDlg, IDC_CHOOSE_CP_NOTE), IsDlgButtonChecked(hDlg, cmdId) );

            break;


        };

        break;

    case WM_NOTIFY:

        if (((NMHDR *) lParam)->code == PSN_WIZNEXT) {

            //
            // Remember the cover page settings selected
            //

            pUserMem->noteSubjectFlag = 0;
            pUserMem->cpPaperSize = 0;
            pUserMem->cpOrientation = 0;
            pdmPrivate->sendCoverPage = IsDlgButtonChecked(hDlg, IDC_CHOOSE_CP_CHECK);

            GetSelectedCoverPage(pUserMem->pCPInfo,
                                 GetDlgItem(hDlg, IDC_CHOOSE_CP_LIST),
                                 pUserMem->coverPage);

            if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE)) {

                SetRegistryDword(hRegKey, REGVAL_SEND_COVERPG, pdmPrivate->sendCoverPage);
                SetRegistryString(hRegKey, REGVAL_COVERPG, pUserMem->coverPage);
                RegCloseKey(hRegKey);
            }

            //
            // If a cover page is selected, then do the following:
            //  if the cover page file is a link resolve it
            //  check if the cover page file contains note/subject fields
            //

            if (pdmPrivate->sendCoverPage)
                ValidateSelectedCoverPage(pUserMem);

            //
            // Collect the current values of other dialog controls
            //

            if (pUserMem->pSubject) MemFree(pUserMem->pSubject);
            if (pUserMem->pNoteMessage) MemFree(pUserMem->pNoteMessage);
            pUserMem->pSubject = GetTextStringValue(GetDlgItem(hDlg, IDC_CHOOSE_CP_SUBJECT));
            pUserMem->pNoteMessage = GetTextStringValue(GetDlgItem(hDlg, IDC_CHOOSE_CP_NOTE));

            //
            // If the current application is "Send Note" utility, then
            // the note field must not be empty.
            //

            if (pUserMem->pSubject == NULL &&
                pUserMem->pNoteMessage == NULL &&
                GetEnvironmentVariable(TEXT("NTFaxSendNote"), NULL, 0))
            {
                DisplayMessageDialog(hDlg, 0, 0, IDS_NOTE_SUBJECT_EMPTY);
                SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_CP_SUBJECT));
                SetWindowLongPtr(hDlg, DWLP_MSGRESULT, -1);                
                return TRUE;
            }

        }

        return FALSE;
    }

    return TRUE;
}



INT_PTR
FaxOptsWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the first wizard page: entering subject and note information

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

{
    //
    // Maximum length for various text fields
    //

    static INT  textLimits[] = {

        IDC_WIZ_FAXOPTS_BILLING,   16,        
        0
    };

    PUSERMEM    pUserMem;
    PDMPRIVATE  pdmPrivate;
    WORD        cmdId;
 //   TCHAR       TimeFormat[32];
    TCHAR           Is24H[2], IsRTL[2], *pszTimeFormat = TEXT("h : mm tt");
    static HWND hTimeControl;
    SYSTEMTIME  st;

    if (! (pUserMem = CommonWizardProc(hDlg, message, wParam, lParam, PSWIZB_BACK|PSWIZB_NEXT)))
        return FALSE;


    pdmPrivate = &pUserMem->devmode.dmPrivate;
    switch (message) {

    case WM_INITDIALOG:
        

        //LoadString(ghInstance,IDS_WIZ_TIME_FORMAT,TimeFormat,sizeof(TimeFormat));
        hTimeControl = GetDlgItem(hDlg, IDC_WIZ_FAXOPTS_SENDTIME);

        if (GetLocaleInfo( LOCALE_USER_DEFAULT,LOCALE_ITIME, Is24H,sizeof(Is24H) ) && Is24H[0] == TEXT('1')) {
            pszTimeFormat = TEXT("H : mm");
        }
        else if (GetLocaleInfo( LOCALE_USER_DEFAULT,LOCALE_ITIMEMARKPOSN, IsRTL,sizeof(IsRTL) ) && IsRTL[0] == TEXT('1')) {
            pszTimeFormat = TEXT("tt h : mm");
        }
        
        LimitTextFields(hDlg, textLimits);

        DateTime_SetFormat( hTimeControl,pszTimeFormat );

        
    
    

        //
        // restore time to send controls
        //
        cmdId = (pdmPrivate->whenToSend == SENDFAX_AT_CHEAP) ? IDC_WIZ_FAXOPTS_DISCOUNT : 
                (pdmPrivate->whenToSend == SENDFAX_AT_TIME) ? IDC_WIZ_FAXOPTS_SPECIFIC :
                IDC_WIZ_FAXOPTS_ASAP;

        CheckDlgButton(hDlg, cmdId, TRUE);
        
        EnableWindow(hTimeControl, (cmdId == IDC_WIZ_FAXOPTS_SPECIFIC) );
                    
        EnableWindow(GetDlgItem(hDlg, IDC_STATIC_FAXOPTS_DATE), (cmdId == IDC_WIZ_FAXOPTS_SPECIFIC) );

        GetLocalTime(&st);
        st.wHour = pdmPrivate->sendAtTime.Hour;
        st.wMinute = pdmPrivate->sendAtTime.Minute;
        DateTime_SetSystemtime( hTimeControl, GDT_VALID, &st );
                    
        SetDlgItemText(hDlg, IDC_WIZ_FAXOPTS_BILLING, pdmPrivate->billingCode);
        
        return TRUE;

    case WM_NOTIFY:

        if (((NMHDR *) lParam)->code == PSN_WIZNEXT) {
            //
            // retrieve the billing code
            //
            if (! GetDlgItemText(hDlg, IDC_WIZ_FAXOPTS_BILLING, pdmPrivate->billingCode, MAX_BILLING_CODE)) {
                pdmPrivate->billingCode[0] = NUL;
            }
            
            //
            // retrieve the sending time
            //
            pdmPrivate->whenToSend = IsDlgButtonChecked(hDlg,IDC_WIZ_FAXOPTS_DISCOUNT) ? SENDFAX_AT_CHEAP :
                                     IsDlgButtonChecked(hDlg,IDC_WIZ_FAXOPTS_SPECIFIC) ? SENDFAX_AT_TIME :
                                     SENDFAX_ASAP;

            if (pdmPrivate->whenToSend == SENDFAX_AT_TIME) {
                DWORD rVal;
                SYSTEMTIME SendTime;                
                TCHAR TimeBuffer[128];

                
                //
                // get specific time
                //                
                rVal = DateTime_GetSystemtime(hTimeControl, &SendTime);
                pdmPrivate->sendAtTime.Hour = SendTime.wHour;
                pdmPrivate->sendAtTime.Minute = SendTime.wMinute;
            
                    
                rVal = GetDateFormat(
                    LOCALE_SYSTEM_DEFAULT,
                    0,
                    &SendTime,
                    NULL,
                    TimeBuffer,
                    sizeof(TimeBuffer)
                    );
                
                TimeBuffer[rVal - 1] = TEXT(' ');

                GetTimeFormat(
                    LOCALE_SYSTEM_DEFAULT,
                    0,
                    &SendTime,
                    NULL,
                    &TimeBuffer[rVal],
                    sizeof(TimeBuffer)
                    );
                
                Verbose(("faxui - Fax Send time %ws", TimeBuffer));
            }

        }

        break;
    
    case WM_COMMAND:
        switch (cmdId = GET_WM_COMMAND_ID(wParam, lParam)) {

        case IDC_WIZ_FAXOPTS_SPECIFIC:
        case IDC_WIZ_FAXOPTS_DISCOUNT:
        case IDC_WIZ_FAXOPTS_ASAP:
            EnableWindow(hTimeControl, (cmdId == IDC_WIZ_FAXOPTS_SPECIFIC) );
            EnableWindow(GetDlgItem(hDlg, IDC_STATIC_FAXOPTS_DATE), (cmdId == IDC_WIZ_FAXOPTS_SPECIFIC) );

            break;

        };

        break;
    default: 
        return FALSE;
    } ;
    return TRUE;
}


#ifdef FAX_SCAN_ENABLED

BOOL
CloseDataSource(
    PUSERMEM pUserMem,
    TW_IDENTITY * TwIdentity
    );

BOOL
OpenDataSource(
    PUSERMEM pUserMem,
    TW_IDENTITY * TwIdentity
    );

BOOL
DisableDataSource(
    PUSERMEM pUserMem,
    TW_USERINTERFACE * TwUserInterface
    );

BOOL
EnableDataSource(
    PUSERMEM pUserMem,
    TW_USERINTERFACE * TwUserInterface
    );

BOOL
SetCapability(
    PUSERMEM pUserMem,
    USHORT Capability,
    USHORT Type,
    LPVOID Value
    );


BOOL
Scan_SetCapabilities( 
    PUSERMEM pUserMem
    );

#define WM_SCAN_INIT            WM_APP
#define WM_SCAN_OPENDSM         WM_APP+1
#define WM_SCAN_CLOSEDSM        WM_APP+2
#define WM_SCAN_GETDEFAULT      WM_APP+3
#define WM_SCAN_GETFIRST        WM_APP+4
#define WM_SCAN_GETNEXT         WM_APP+5

#define WM_SCAN_OPENDS          WM_APP+10
#define WM_SCAN_CLOSEDS         WM_APP+11
#define WM_SCAN_ENABLEDS        WM_APP+12
#define WM_SCAN_DISABLEDS       WM_APP+13

#define WM_SCAN_CONTROLGET      WM_APP+20
#define WM_SCAN_CONTROLGETDEF   WM_APP+21
#define WM_SCAN_CONTROLRESET    WM_APP+22
#define WM_SCAN_CONTROLSET      WM_APP+23
#define WM_SCAN_CONTROLENDXFER  WM_APP+24

#define WM_SCAN_IMAGEGET        WM_APP+30

#define Scan_Init(_pUserMem)    (SendMessage(_pUserMem->hWndTwain, WM_SCAN_INIT,0,(LPARAM)_pUserMem))
#define Scan_OpenDSM(_pUserMem) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_OPENDSM,0,(LPARAM)&_pUserMem->hWndTwain))
#define Scan_CloseDSM(_pUserMem) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CLOSEDSM,0,(LPARAM)&_pUserMem->hWndTwain))

#define Scan_GetDefault(_pUserMem,_TwIdentity) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_GETDEFAULT,0,(LPARAM)_TwIdentity))
#define Scan_GetFirst(_pUserMem,_TwIdentity) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_GETFIRST,0,(LPARAM)_TwIdentity))
#define Scan_GetNext(_pUserMem,_TwIdentity) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_GETNEXT,0,(LPARAM)_TwIdentity))

#define Scan_OpenDS(_pUserMem,_TwIdentity)  (SendMessage(_pUserMem->hWndTwain, WM_SCAN_OPENDS,0,(LPARAM)_TwIdentity))
#define Scan_CloseDS(_pUserMem,_TwIdentity) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CLOSEDS,0,(LPARAM)_TwIdentity))
#define Scan_EnableDS(_pUserMem,_TwUi)      (SendMessage(_pUserMem->hWndTwain, WM_SCAN_ENABLEDS,0,(LPARAM)_TwUi))
#define Scan_DisableDS(_pUserMem,_TwUi)     (SendMessage(_pUserMem->hWndTwain, WM_SCAN_DISABLEDS,0,(LPARAM)_TwUi))

#define Scan_ControlGet(_pUserMem,_What,_Data) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CONTROLGET,(WPARAM)_What,(LPARAM)_Data))
#define Scan_ControlGetDef(_pUserMem,_What,_Data) (SendMessage(_pUserMem->hWndTwain, WM_SCAN_CONTROLGETDEF,(WPARAM)_What,(LPARAM)_Data))
#define Scan_ControlReset(_pUserMem,_What,_Data) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CONTROLRESET,(WPARAM)_What,(LPARAM)_Data))
#define Scan_ControlSet(_pUserMem,_What,_Data) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CONTROLSET,(WPARAM)_What,(LPARAM)_Data))
#define Scan_ControlEndXfer(_pUserMem,_What,_Data) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_CONTROLENDXFER,(WPARAM)_What,(LPARAM)_Data))

#define Scan_ImageGet(_pUserMem,_What,_Data) ((DWORD)SendMessage(_pUserMem->hWndTwain, WM_SCAN_IMAGEGET,(WPARAM)_What,(LPARAM)_Data))

//#define WM_SCAN_GETEVENT        WM_APP+7



DWORD
CallTwain(
    PUSERMEM  pUserMem,
    TW_UINT32 DG,
    TW_UINT16 DAT,
    TW_UINT16 MSG,
    TW_MEMREF pData
    )
{
    DWORD TwResult = 0;
    TW_STATUS TwStatus = {0};

    __try {

        TwResult = pUserMem->pDsmEntry(
            &pUserMem->AppId,
            NULL,
            DG,
            DAT,
            MSG,
            pData
            );

        if (TwResult) {

            pUserMem->pDsmEntry(
                &pUserMem->AppId,
                NULL,
                DG_CONTROL,
                DAT_STATUS,
                MSG_GET,
                (TW_MEMREF) &TwStatus
                );
            if (TwStatus.ConditionCode) {
                TwResult = TwStatus.ConditionCode;
            }

            Verbose(( "CallTwain failed, ec=%d\n", TwResult ));
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        TwResult = GetExceptionCode();
        Verbose(( "CallTwain crashed, ec=0x%08x\n", TwResult ));

    }

    return TwResult;
}


DWORD
CallTwainDataSource(
    PUSERMEM  pUserMem,
    TW_UINT32 DG,
    TW_UINT16 DAT,
    TW_UINT16 MSG,
    TW_MEMREF pData
    )
{
    DWORD TwResult = 0;
    TW_STATUS TwStatus = {0};

    __try {

        TwResult = pUserMem->pDsmEntry(
            &pUserMem->AppId,
            &pUserMem->DataSource,
            DG,
            DAT,
            MSG,
            pData
            );

        if (TwResult) {

            pUserMem->pDsmEntry(
                &pUserMem->AppId,
                &pUserMem->DataSource,
                DG_CONTROL,
                DAT_STATUS,
                MSG_GET,
                (TW_MEMREF) &TwStatus
                );
            if (TwStatus.ConditionCode) {
                TwResult = TwStatus.ConditionCode;
            }

            //Verbose(( "CallTwainDataSource failed, ec=%d\n", TwResult ));
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        TwResult = GetExceptionCode();
        Verbose(( "CallTwainDataSource crashed, ec=0x%08x\n", TwResult ));

    }

    return TwResult;
}


LRESULT
WINAPI
TwainWndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    DWORD TwResult = 0;
    static PUSERMEM pUserMem = NULL;

    switch (uMsg) {
        
        case WM_SCAN_INIT:
            pUserMem = (PUSERMEM)lParam;
            return(TRUE);
            break;
                    
        case WM_SCAN_OPENDSM :
            TwResult = CallTwain(
                            pUserMem,
                            DG_CONTROL,
                            DAT_PARENT,
                            MSG_OPENDSM,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_OPENDSM (DAT_PARENT) failed, ec = %d\n", TwResult ));
            }
            return(TwResult);
            break;

        case WM_SCAN_CLOSEDSM:
            TwResult = CallTwain(
                            pUserMem,
                            DG_CONTROL,
                            DAT_PARENT,
                            MSG_CLOSEDSM,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_CLOSEDSM (DAT_PARENT) failed, ec = %d\n", TwResult ));
            } 
            return(TwResult);
            break;

        case WM_SCAN_GETDEFAULT:
            TwResult = CallTwain(
                            pUserMem,
                            DG_CONTROL,
                            DAT_IDENTITY,
                            MSG_GETDEFAULT,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GETDEFAULT (DAT_IDENTITY) failed, ec = %d\n", TwResult ));
            }
            return(TwResult);
            break;

        case WM_SCAN_GETFIRST:
            TwResult = CallTwain(
                            pUserMem,
                            DG_CONTROL,
                            DAT_IDENTITY,
                            MSG_GETFIRST,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GETFIRST (DAT_IDENTITY) failed, ec = %d\n", TwResult ));
            }
            return(TwResult);
            break;
            
        case WM_SCAN_GETNEXT:
            TwResult = CallTwain(
                            pUserMem,
                            DG_CONTROL,
                            DAT_IDENTITY,
                            MSG_GETNEXT,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS && TwResult != TWRC_ENDOFLIST) {
                Verbose(( "MSG_GETNEXT (DAT_IDENTITY) failed, ec = %d\n", TwResult ));
            }
            return(TwResult);
            break;
            
        case WM_SCAN_OPENDS:
            if (OpenDataSource( pUserMem,  (TW_IDENTITY *)lParam ) &&
                Scan_SetCapabilities( pUserMem )) {
                return TRUE;
            }
            return(FALSE);
        
        case WM_SCAN_CLOSEDS:
            return (CloseDataSource( pUserMem, (TW_IDENTITY *)lParam ));
        
        case WM_SCAN_ENABLEDS:
            return (EnableDataSource( pUserMem, (TW_USERINTERFACE *)lParam ));
        
        case WM_SCAN_DISABLEDS:
            return (DisableDataSource( pUserMem, (TW_USERINTERFACE *)lParam ));
        
        case WM_SCAN_CONTROLGET:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_CONTROL,
                            (TW_UINT16)wParam,
                            MSG_GET,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GET (%d)failed, ec = %d\n", wParam, TwResult ));
            }
            return(TwResult);
            break;
        case WM_SCAN_CONTROLGETDEF:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_CONTROL,
                            (TW_UINT16)wParam,
                            MSG_GETDEFAULT,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GETDEFAULT (%d)failed, ec = %d\n", wParam, TwResult ));
            }
            return(TwResult);
            break;
        case WM_SCAN_CONTROLRESET:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_CONTROL,
                            (TW_UINT16)wParam,
                            MSG_RESET,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_RESET (%d)failed, ec = %d\n", wParam, TwResult ));
            }
            return(TwResult);
            break;
        case WM_SCAN_CONTROLSET:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_CONTROL,
                            (TW_UINT16)wParam,
                            MSG_SET,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_SET (%d)failed, ec = %d\n", wParam, TwResult ));
            }
            return(TwResult);
            break;
        case WM_SCAN_CONTROLENDXFER:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_CONTROL,
                            (TW_UINT16)wParam,
                            MSG_ENDXFER,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_ENDXFER (%d)failed, ec = %d\n", wParam, TwResult ));
            }
            return(TwResult);
            break;
    
        case WM_SCAN_IMAGEGET:
            TwResult = CallTwainDataSource(
                            pUserMem,
                            DG_IMAGE,
                            (TW_UINT16)wParam,
                            MSG_GET,
                            (TW_MEMREF) lParam
                            );
            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GET (DAT_IMAGEINFO) failed, ec = %d\n", TwResult ));
            }
            return(TwResult);
            break;
    
    default:

        return DefWindowProc( hwnd, uMsg, wParam, lParam );


    };

    Assert(FALSE);
    return FALSE;
        
}


DWORD
TwainMessagePumpThread(
    PUSERMEM pUserMem
    )
{
    WNDCLASS wc;
    MSG msg;
    HWND hWnd;
    DWORD WaitObj;
    TW_EVENT TwEvent;
    DWORD TwResult;


    wc.style = CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC)TwainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = ghInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"FaxWizTwainWindow";

    if (RegisterClass( &wc ) == 0) {
        SetEvent( pUserMem->hEvent );
        return 0;
    }

    hWnd = CreateWindow(
        L"FaxWizTwainWindow",
        L"FaxWizTwainWindow",
        WS_DISABLED,
        0,
        0,
        0,
        0,
        NULL,
        NULL,
        ghInstance,
        NULL
        );
    if (hWnd == NULL) {
        SetEvent( pUserMem->hEvent );
        return 0;
    }

    pUserMem->hWndTwain = hWnd;

    Scan_Init(pUserMem);

    SetEvent( pUserMem->hEvent );

    while (TRUE) {
        WaitObj = MsgWaitForMultipleObjectsEx( 1, &pUserMem->hEventQuit, INFINITE, QS_ALLINPUT, 0 );
        if (WaitObj == WAIT_OBJECT_0) {
            return 0;
        }
        
        // PeekMessage instead of GetMessage so we drain the message queue
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE )) {
            if (msg.message == WM_QUIT) {
                return 0;
            }
        


            if (pUserMem->TwainAvail) {
    
                TwEvent.pEvent = (TW_MEMREF) &msg;
                TwEvent.TWMessage = MSG_NULL;
    
                TwResult = CallTwainDataSource(
                    pUserMem,
                    DG_CONTROL,
                    DAT_EVENT,
                    MSG_PROCESSEVENT,
                    (TW_MEMREF) &TwEvent
                    );

                //if (TwResult != TWRC_SUCCESS && TwResult != TWRC_NOTDSEVENT) {
                //    Verbose(( "MSG_PROCESSEVENT (DAT_EVENT) failed, ec=%d\n", TwResult ));
                //}
    
                switch (TwEvent.TWMessage) {
                    case MSG_XFERREADY:
                        //
                        // transition from state 5 to state 6
                        //
                        Verbose(( "received MSG_XFERREADY, setting hEventXfer\n" ));
                        SetEvent( pUserMem->hEventXfer );
                        break;
    
                    case MSG_CLOSEDSREQ:
                        //
                        // transition from state 5 to 4 to 3
                        //
                        
                        pUserMem->TwainCancelled = TRUE;
                        Verbose(( "received MSG_CLOSEDSREQ, setting hEventXfer\n" ));
                        SetEvent( pUserMem->hEventXfer );
                        break;
    
                    case MSG_NULL:
                        break;
                }
    
                if (TwResult == TWRC_NOTDSEVENT) {
                    TranslateMessage( &msg );
                    DispatchMessage( &msg );
                }
            }
        }
    }

    return 0;
}


VOID
TerminateTwain(
    PUSERMEM pUserMem
    )
{
    DWORD TwResult;

    Verbose(( "entering TerminateTwain, state = %d\n", pUserMem->State ));

    //Scan_CloseDS( pUserMem, &pUserMem->DataSource );
    CloseDataSource( pUserMem, &pUserMem->DataSource );

    if (pUserMem->State == 3) {

        TwResult = Scan_CloseDSM( pUserMem );

        if (TwResult == TWRC_SUCCESS) {
            pUserMem->State = 2;
            Verbose(( "entering state 2\n" ));
        }
    }

    if (pUserMem->hWndTwain) {
        DestroyWindow( pUserMem->hWndTwain );
    }

    SetEvent( pUserMem->hEventQuit );

    WaitForSingleObject( pUserMem->hThread, INFINITE );

    if (pUserMem->hTwain) {
        FreeLibrary( pUserMem->hTwain );
    }

    CloseHandle( pUserMem->hThread );
}


#if 0
BOOL
SetCapability(
    PUSERMEM pUserMem,
    USHORT Capability,
    USHORT Type,
    LPVOID Value
    )
{
    DWORD TwResult;
    TW_CAPABILITY TwCapability;
    TW_ONEVALUE *TwOneValue;
    TW_FIX32 *TwFix32;



    TwCapability.Cap = Capability;
    TwCapability.ConType = TWON_ONEVALUE;
    TwCapability.hContainer = GlobalAlloc( GHND, sizeof(TW_ONEVALUE) );

    TwOneValue = (TW_ONEVALUE*) GlobalLock( TwCapability.hContainer );

    TwOneValue->ItemType = Type;

    if (Type == TWTY_FIX32) {
        TwFix32 = (TW_FIX32*)Value;
        CopyMemory( &TwOneValue->Item, TwFix32, sizeof(TW_FIX32) );
    } else {
        TwOneValue->Item = (DWORD)Value;
    }

    GlobalUnlock( TwCapability.hContainer );

    //
    // bugbug called in opendatasource
    //
    TwResult  = CallTwainDataSource(
                    pUserMem,
                    DG_CONTROL,
                    DAT_CAPABILITY,
                    MSG_SET,
                    (TW_MEMREF) &TwCapability
                    );
    
    GlobalFree( TwCapability.hContainer );

    if (TwResult != TWRC_SUCCESS) {
        Verbose(( "Could not set capability 0x%04x\n", Capability ));
        return FALSE;
    }

    return TRUE;
}
#else
BOOL
SetCapability(
    PUSERMEM pUserMem,
    USHORT Capability,
    USHORT Type,
    LPVOID Value
    )
{
    DWORD TwResult;
    TW_CAPABILITY TwCapability;
    TW_ONEVALUE *TwOneValue;
    TW_FIX32 *TwFix32;



    TwCapability.Cap = Capability;
    TwCapability.ConType = TWON_ONEVALUE;
    TwCapability.hContainer = GlobalAlloc( GHND, sizeof(TW_ONEVALUE) );

    TwOneValue = (TW_ONEVALUE*) GlobalLock( TwCapability.hContainer );

    TwOneValue->ItemType = Type;

    if (Type == TWTY_FIX32) {
        TwFix32 = (TW_FIX32*)Value;
        CopyMemory( &TwOneValue->Item, TwFix32, sizeof(TW_FIX32) );
    } else {
        TwOneValue->Item = PtrToUlong(Value);
    }

    GlobalUnlock( TwCapability.hContainer );

    //
    // bugbug called in opendatasource
    //
    TwResult = Scan_ControlSet(pUserMem, DAT_CAPABILITY, &TwCapability);
    
    GlobalFree( TwCapability.hContainer );

    if (TwResult != TWRC_SUCCESS) {
        Verbose(( "Could not set capability 0x%04x\n", Capability ));
        return FALSE;
    }

    return TRUE;
}
#endif

float
FIX32ToFloat(
    TW_FIX32 fix32
    )
{
    float   floater;
    floater = (float) fix32.Whole + (float) fix32.Frac / (float) 65536.0;
    return floater;
}


BOOL
OpenDataSource(
    PUSERMEM pUserMem,
    TW_IDENTITY * TwIdentity

    )
{
    DWORD TwResult;

    Verbose(( "entering OpenDataSource, state = %d\n", pUserMem->State ));

    if (pUserMem->State == 3) {

        //
        // open the data source
        //

        TwResult = CallTwain(
            pUserMem,
            DG_CONTROL,
            DAT_IDENTITY,
            MSG_OPENDS,
            (TW_MEMREF) TwIdentity
            );
        if (TwResult != TWRC_SUCCESS) {
            return FALSE;
        }

        pUserMem->State = 4;
        Verbose(( "entering state 4\n" ));
    }

    return TRUE;

}

BOOL
Scan_SetCapabilities(
    PUSERMEM  pUserMem
    )
{
    //
    // set the capabilities
    //

    SetCapability( pUserMem, CAP_XFERCOUNT,           TWTY_INT16,  (LPVOID)1               );
    SetCapability( pUserMem, ICAP_PIXELTYPE,          TWTY_INT16,  (LPVOID)TWPT_BW         );
    SetCapability( pUserMem, ICAP_BITDEPTH,           TWTY_INT16,  (LPVOID)1               );
    SetCapability( pUserMem, ICAP_BITORDER,           TWTY_INT16,  (LPVOID)TWBO_MSBFIRST   );
    SetCapability( pUserMem, ICAP_PIXELFLAVOR,        TWTY_INT16,  (LPVOID)TWPF_VANILLA    );
    SetCapability( pUserMem, ICAP_XFERMECH,           TWTY_INT16,  (LPVOID)TWSX_MEMORY     );

    return TRUE;
}


BOOL
EnableDataSource(
    PUSERMEM  pUserMem,
    TW_USERINTERFACE *TwUserInterface
    )
{
    DWORD TwResult;
    
    if (pUserMem->State == 4) {

        ResetEvent( pUserMem->hEventXfer );

        //
        // enable the data source's user interface
        //

        pUserMem->TwainCancelled = FALSE;
        
        TwResult = CallTwainDataSource(
            pUserMem,
            DG_CONTROL,
            DAT_USERINTERFACE,
            MSG_ENABLEDS,
            (TW_MEMREF) TwUserInterface
            );
        if (TwResult != TWRC_SUCCESS) {
            Verbose(( "MSG_ENABLEDS (DAT_USERINTERFACE) failed, ec=%d\n",TwResult ));
            return FALSE;
        }

        pUserMem->State = 5;

        Verbose(( "entering state 5\n" ));
    }

    return TRUE;
}

BOOL
DisableDataSource(
    PUSERMEM pUserMem,
    TW_USERINTERFACE * TwUserInterface
    )
{
    DWORD TwResult;
    
    Verbose(( "entering DisableDataSource, state = %d\n",pUserMem->State ));

    if (pUserMem->State == 5) {

        //
        // disable the data source
        //

        TwResult = CallTwainDataSource(
            pUserMem,
            DG_CONTROL,
            DAT_USERINTERFACE,
            MSG_DISABLEDS,
            (TW_MEMREF) TwUserInterface
            );
        if (TwResult != TWRC_SUCCESS) {
            Verbose(( "MSG_DISABLEDS (DAT_USERINTERFACE) failed, ec = %d\n", TwResult ));
            return FALSE;
        }

        pUserMem->State = 4;

        Verbose(( "entering state 4\n" ));
    }

    return TRUE;
}


BOOL
CloseDataSource(
    PUSERMEM pUserMem,
    TW_IDENTITY * TwIdentity
    )
{
    DWORD TwResult;
 
    Verbose(( "entering CloseDataSource, state = %d\n",pUserMem->State ));
 
    if (pUserMem->State == 4) {

        //
        // close the data source
        //

        TwResult = CallTwain(
            pUserMem,
            DG_CONTROL,
            DAT_IDENTITY,
            MSG_CLOSEDS,
            (TW_MEMREF) TwIdentity
            );
        if (TwResult != TWRC_SUCCESS) {
            Verbose(( "MSG_CLOSEDS (DAT_IDENTIFY) failed, ec = %d\n", TwResult ));
            return FALSE;
        }

        pUserMem->State = 3;

        Verbose(( "entering state 3\n" ));
    }

    Verbose(( "leaving CloseDataSource, state = %d\n",pUserMem->State ));

    return TRUE;
}


BOOL
InitializeTwain(
    PUSERMEM pUserMem
    )
{
    BOOL Rval = FALSE;
    DWORD TwResult;
    DWORD ThreadId;


    HKEY hKey;
    hKey = OpenRegistryKey( HKEY_LOCAL_MACHINE, REGKEY_SOFTWARE, FALSE, REG_READONLY );
    if (hKey) {
        if (GetRegistryDword( hKey, REGVAL_SCANNER_SUPPORT ) != 0) {
            RegCloseKey( hKey );
            return FALSE;
        }
        RegCloseKey( hKey );
    }

    pUserMem->State = 1;
    Verbose(( "entering state 1\n" ));

    pUserMem->hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
    pUserMem->hEventQuit = CreateEvent( NULL, TRUE, FALSE, NULL );
    pUserMem->hEventXfer = CreateEvent( NULL, TRUE, FALSE, NULL );

    pUserMem->hThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)TwainMessagePumpThread,
        pUserMem,
        0,
        &ThreadId
        );
    if (!pUserMem->hThread) {
        goto exit;
    }

    WaitForSingleObject( pUserMem->hEvent, INFINITE );

    if (pUserMem->hWndTwain == NULL) {
        goto exit;
    }

    pUserMem->hTwain = LoadLibrary( L"twain_32.dll" );
    if (pUserMem->hTwain) {
        pUserMem->pDsmEntry = (DSMENTRYPROC) GetProcAddress( pUserMem->hTwain, "DSM_Entry" );
    }

    if (pUserMem->pDsmEntry == NULL) {
        goto exit;
    }

    pUserMem->State = 2;

    Verbose(( "entering state 2\n" ));

    //
    // open the data source manager
    //

    pUserMem->AppId.Id = 0;
    pUserMem->AppId.Version.MajorNum = 1;
    pUserMem->AppId.Version.MinorNum = 0;
    pUserMem->AppId.Version.Language = TWLG_USA;
    pUserMem->AppId.Version.Country = TWCY_USA;
    strcpy( pUserMem->AppId.Version.Info, "Fax Print Wizard" );
    pUserMem->AppId.ProtocolMajor = TWON_PROTOCOLMAJOR;
    pUserMem->AppId.ProtocolMinor = TWON_PROTOCOLMINOR;
    pUserMem->AppId.SupportedGroups = DG_IMAGE | DG_CONTROL;
    strcpy( pUserMem->AppId.Manufacturer, "Microsoft" );
    strcpy( pUserMem->AppId.ProductFamily, "Windows" );
    strcpy( pUserMem->AppId.ProductName, "Windows" );

    TwResult = Scan_OpenDSM( pUserMem );

    if (TwResult != TWRC_SUCCESS) {
        Verbose(( "MSG_OPENDSM (DAT_PARENT) failed, ec = %d\n", TwResult ));
        goto exit;
    }

    pUserMem->State = 3;
    
    Verbose(( "entering state 3\n" ));

    //
    // select the default data source
    //

    TwResult = Scan_GetDefault(pUserMem, &pUserMem->DataSource);
        
    if (TwResult != TWRC_SUCCESS) {
        Verbose(( "MSG_GETDEFAULT (DAT_IDENTITY) failed, ec = %d\n", TwResult )); 
        goto exit;
    }

    //
    // return success
    //

    Rval = TRUE;
    pUserMem->TwainAvail = TRUE;

exit:

    if (!Rval) {

        //
        // part of the initialization failed
        // so lets clean everything up before exiting
        //

        TerminateTwain( pUserMem );
    }

    return Rval;
}


DWORD
ScanningThread(
    PUSERMEM pUserMem
    )
{
    DWORD               TwResult;
    TW_SETUPMEMXFER     TwSetupMemXfer;
    TW_IMAGEMEMXFER     TwImageMemXfer;
    TW_PENDINGXFERS     TwPendingXfers;
    TW_USERINTERFACE    TwUserInterface;
    TW_IMAGEINFO        TwImageInfo;
    HANDLE              hFile = INVALID_HANDLE_VALUE;
    TIFF_HEADER         TiffHdr;
    DWORD               FileBytes = 0;
    DWORD               BytesWritten;
    LPBYTE              Buffer1 = NULL;
    LPBYTE              CurrBuffer;
    LPBYTE              p = NULL;
    DWORD               LineSize;
    DWORD               i;
    DWORD               Lines;
    LPBYTE              ScanBuffer = NULL;
    DWORD               IfdOffset;
    DWORD               NextIfdSeekPoint;
    WORD                NumDirEntries;
    DWORD               DataOffset;
    BOOL                FirstTime = TRUE;


    Verbose(( "Entering ScanningThread, state = %d\n", pUserMem->State ));
    pUserMem->TwainActive = TRUE;

    if (pUserMem->State == 5) {

        while (pUserMem->State == 5) {
            //
            // wait for the MSG_XFERREADY message to be
            // delivered to the message pump.  this moves
            // us to state 6
            //

            WaitForSingleObject( pUserMem->hEventXfer, INFINITE );
            ResetEvent( pUserMem->hEventXfer );
            Verbose(( "hEventXfer signalled\n" ));
            if (pUserMem->TwainCancelled) {
                Verbose(( "twain cancelled\n" ));
                TwUserInterface.ShowUI  = FALSE;
                TwUserInterface.ModalUI = FALSE;
                TwUserInterface.hParent = NULL;
                Scan_DisableDS(pUserMem, &TwUserInterface);                
                goto exit;
            }

            //
            // get the image info
            //

            TwResult = Scan_ImageGet(pUserMem,DAT_IMAGEINFO,&TwImageInfo);

            if (TwResult != TWRC_SUCCESS) {
                Verbose(( "MSG_GET (DAT_IMAGEINFO) failed, ec=%d\n", TwResult ));
                goto exit;
            }

            //
            // we only allow monochrome images
            //

            if (TwImageInfo.BitsPerPixel > 1) {

                DisplayMessageDialog( NULL, 
                                      MB_ICONASTERISK | MB_OK | MB_SETFOREGROUND,
                                      IDS_SCAN_ERROR_TITLE, 
                                      IDS_SCAN_ERROR_BW );

                //
                // go back to state 5
                //

                TwResult = Scan_ControlReset(pUserMem,DAT_PENDINGXFERS,&TwPendingXfers);

                if (TwResult != TWRC_SUCCESS) {
                    Verbose(( "MSG_RESET (DAT_PENDINGXFERS) failed, ec=%d\n", TwResult ));
                    goto exit;
                }

            } else {
                pUserMem->State = 6;
                Verbose(( "entering state 6\n" ));
            }
        }

        //
        // setup the memory buffer sizes
        //
        
        TwResult = Scan_ControlGet(pUserMem,DAT_SETUPMEMXFER, &TwSetupMemXfer);

        if (TwResult != TWRC_SUCCESS) {
            Verbose(( "MSG_GET (DAT_SETUPMEMXFER) failed, ec=%d\n", TwResult ));
            goto exit;
        }

        Buffer1 = (LPBYTE) MemAlloc( TwSetupMemXfer.Preferred );

        TwImageMemXfer.Compression      = TWON_DONTCARE16;
        TwImageMemXfer.BytesPerRow      = TWON_DONTCARE32;
        TwImageMemXfer.Columns          = TWON_DONTCARE32;
        TwImageMemXfer.Rows             = TWON_DONTCARE32;
        TwImageMemXfer.XOffset          = TWON_DONTCARE32;
        TwImageMemXfer.YOffset          = TWON_DONTCARE32;
        TwImageMemXfer.BytesWritten     = TWON_DONTCARE32;
        TwImageMemXfer.Memory.Flags     = TWMF_APPOWNS | TWMF_POINTER;
        TwImageMemXfer.Memory.Length    = TwSetupMemXfer.Preferred;
        TwImageMemXfer.Memory.TheMem    = Buffer1;
    }

    Verbose(( "begin transferring image... " ));
    Verbose(( "entering state 7, TwResult = %d\n", TwResult ));

    while (TwResult != TWRC_XFERDONE) {
try_again:    
        pUserMem->State = 7;
        
        TwResult = Scan_ImageGet(pUserMem,DAT_IMAGEMEMXFER,&TwImageMemXfer);

        if (TwResult == 0) {
            if (ScanBuffer == NULL) {
                ScanBuffer = VirtualAlloc(
                    NULL,
                    TwImageMemXfer.BytesPerRow * (TwImageInfo.ImageLength + 16),
                    MEM_COMMIT,
                    PAGE_READWRITE
                    );
                if (ScanBuffer == NULL) {
                    goto exit;
                }
                p = ScanBuffer;
            }
            CopyMemory( p, Buffer1, TwImageMemXfer.BytesWritten );
            p += TwImageMemXfer.BytesWritten;
            FileBytes += TwImageMemXfer.BytesWritten;
        } else if ( TwResult != TWRC_XFERDONE) {
            Verbose(( "MGS_GET (DAT_IMAGEMEMXFER) failed, ec = %d ", TwResult ));
            goto exit;
        }
    }

    
    Assert( TwResult == TWRC_XFERDONE );

    if (!ScanBuffer || !FileBytes) {
        //
        // we didn't really scan anything...
        //
        Verbose(( "asked for tranfer, but nothing was transferred\n" ));
        if (FirstTime) {
            FirstTime = FALSE;
            goto try_again;
        }   

        TwResult = Scan_ControlEndXfer(pUserMem,DAT_PENDINGXFERS, &TwPendingXfers);
        
        pUserMem->State = 5;

        //
        // disable the data source's user interface
        //
    
        TwUserInterface.ShowUI  = FALSE;
        TwUserInterface.ModalUI = FALSE;
        TwUserInterface.hParent = NULL;
    
        Scan_DisableDS(pUserMem, &TwUserInterface);
        
        pUserMem->State = 4;
    
        Verbose(( "entering state 4\n" ));

        goto exit;
    }

    pUserMem->PageCount += 1;

    Verbose(( "...finished transferring image\n" ));

    hFile = CreateFile(
        pUserMem->FileName,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
        );
    if (hFile == INVALID_HANDLE_VALUE) {
        Verbose(( "CreateFile failed, ec=%d\n", GetLastError() ));
        goto exit;
    }

    if (pUserMem->PageCount == 1) {
        TiffHdr.Identifier = TIFF_LITTLEENDIAN;
        TiffHdr.Version = TIFF_VERSION;
        TiffHdr.IFDOffset = 0;
        WriteFile( hFile, &TiffHdr, sizeof(TiffHdr), &BytesWritten, NULL );
    } else {
        ReadFile( hFile, &TiffHdr, sizeof(TiffHdr), &BytesWritten, NULL );
        NextIfdSeekPoint = TiffHdr.IFDOffset;
        while (NextIfdSeekPoint) {
            SetFilePointer( hFile, NextIfdSeekPoint, NULL, FILE_BEGIN );
            ReadFile( hFile, &NumDirEntries, sizeof(NumDirEntries), &BytesWritten, NULL );
            SetFilePointer( hFile, NumDirEntries*sizeof(TIFF_TAG), NULL, FILE_CURRENT );
            ReadFile( hFile, &NextIfdSeekPoint, sizeof(NextIfdSeekPoint), &BytesWritten, NULL );
        }
    }

    NextIfdSeekPoint = SetFilePointer( hFile, 0, NULL, FILE_CURRENT ) - sizeof(NextIfdSeekPoint);
    SetFilePointer( hFile, 0, NULL, FILE_END );
    DataOffset = SetFilePointer( hFile, 0, NULL, FILE_CURRENT );

    LineSize = TwImageInfo.ImageWidth / 8;
    LineSize += (TwImageInfo.ImageWidth % 8) ? 1 : 0;

    Lines = FileBytes / TwImageMemXfer.BytesPerRow;

    CurrBuffer = ScanBuffer;
    for (i=0; i<Lines; i++) {
        WriteFile( hFile, CurrBuffer, LineSize, &BytesWritten, NULL );
        CurrBuffer += TwImageMemXfer.BytesPerRow;
    }

    IfdOffset = SetFilePointer( hFile, 0, NULL, FILE_CURRENT );

    FaxIFDTemplate.xresNum = (DWORD) FIX32ToFloat( TwImageInfo.XResolution );
    FaxIFDTemplate.yresNum = (DWORD) FIX32ToFloat( TwImageInfo.YResolution );

    FaxIFDTemplate.ifd[IFD_PAGENUMBER].DataOffset = MAKELONG( pUserMem->PageCount-1, 0);
    FaxIFDTemplate.ifd[IFD_IMAGEWIDTH].DataOffset = TwImageInfo.ImageWidth;
    FaxIFDTemplate.ifd[IFD_IMAGELENGTH].DataOffset = Lines;
    FaxIFDTemplate.ifd[IFD_ROWSPERSTRIP].DataOffset = Lines;
    FaxIFDTemplate.ifd[IFD_STRIPBYTECOUNTS].DataOffset = Lines * LineSize;
    FaxIFDTemplate.ifd[IFD_STRIPOFFSETS].DataOffset = DataOffset;
    FaxIFDTemplate.ifd[IFD_XRESOLUTION].DataOffset = IfdOffset + FIELD_OFFSET( FAXIFD, xresNum );
    FaxIFDTemplate.ifd[IFD_YRESOLUTION].DataOffset = IfdOffset + FIELD_OFFSET( FAXIFD, yresNum );
    FaxIFDTemplate.ifd[IFD_SOFTWARE].DataOffset = IfdOffset + FIELD_OFFSET( FAXIFD, software );

    WriteFile( hFile, &FaxIFDTemplate, sizeof(FaxIFDTemplate), &BytesWritten, NULL );

    SetFilePointer( hFile, NextIfdSeekPoint, NULL, FILE_BEGIN );
    WriteFile( hFile, &IfdOffset, sizeof(DWORD), &BytesWritten, NULL );

    //
    // end the transfer
    //

    TwResult = Scan_ControlEndXfer(pUserMem,DAT_PENDINGXFERS, &TwPendingXfers);
        
    if (TwResult != TWRC_SUCCESS) {
        Verbose(( "MSG_ENDXFER (DAT_PENDINGXFERS) failed, ec=%d\n", TwResult ));
        goto exit;
    }

    pUserMem->State = 5;
    Verbose(( "entering state 5\n" ));

    //
    // disable the data source's user interface
    //

    TwUserInterface.ShowUI  = FALSE;
    TwUserInterface.ModalUI = FALSE;
    TwUserInterface.hParent = NULL;

    Scan_DisableDS(pUserMem, &TwUserInterface);
    
    pUserMem->State = 4;

    Verbose(( "entering state 4\n" ));

    PostMessage( pUserMem->hDlgScan, WM_PAGE_COMPLETE, 0, 0 );

exit:
    if (ScanBuffer) {
        VirtualFree( ScanBuffer, 0, MEM_RELEASE);
    }

    if (hFile != INVALID_HANDLE_VALUE) {
        CloseHandle( hFile );
    }

    MemFree( Buffer1 );    

    Verbose(( "leaving scanning thread, state = %d\n", pUserMem->State ));

    pUserMem->TwainActive = FALSE;

    return 0;
}


BOOL
EnumerateDataSources(
    PUSERMEM pUserMem,
    HWND ComboBox,
    HWND StaticText
    )
{
    DWORD TwResult;
    TW_IDENTITY TwIdentity;
    TW_IDENTITY *pDataSource;
    DWORD i;
    DWORD Count = 0;


    TwResult = Scan_GetFirst( pUserMem, &TwIdentity );
        
    if (TwResult != TWRC_SUCCESS) {
        return FALSE;
    }

    do {
        i = (DWORD)SendMessageA( ComboBox, CB_ADDSTRING, 0, (LPARAM)TwIdentity.ProductName );

        pDataSource = (TW_IDENTITY*) MemAlloc( sizeof(TW_IDENTITY) );
        if (pDataSource) {
            CopyMemory( pDataSource, &TwIdentity, sizeof(TW_IDENTITY) );
            SendMessageA( ComboBox, CB_SETITEMDATA, i, (LPARAM)pDataSource );
        }

        Count += 1;

        TwResult = Scan_GetNext( pUserMem, &TwIdentity );

    } while (TwResult == 0);

    i = (DWORD)SendMessageA( ComboBox, CB_FINDSTRINGEXACT, 0, (LPARAM)pUserMem->DataSource.ProductName );
    if (i == CB_ERR) {
        i = 0;
    }

    SendMessageA( ComboBox, CB_SETCURSEL, i, 0 );

    if (Count == 1) {
        EnableWindow( StaticText, FALSE );
        EnableWindow( ComboBox, FALSE );
    }

    return TRUE;
}



INT_PTR
ScanWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the first wizard page: entering subject and note information

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

{
    PUSERMEM pUserMem;
    HANDLE hThread;
    DWORD ThreadId;
    WCHAR TempPath[MAX_PATH];
    //TW_PENDINGXFERS     TwPendingXfers;
    TW_USERINTERFACE    TwUserInterface;


    pUserMem = (PUSERMEM) GetWindowLongPtr(hDlg, DWLP_USER);

    switch (message) {
            
        case WM_INITDIALOG:
            lParam = ((PROPSHEETPAGE *) lParam)->lParam;
            pUserMem = (PUSERMEM) lParam;
            SetWindowLongPtr(hDlg, DWLP_USER, lParam);
            if (GetEnvironmentVariable(L"NTFaxSendNote", TempPath, sizeof(TempPath)) == 0 || TempPath[0] != L'1') {
                pUserMem->TwainAvail = FALSE;
                return TRUE;
            }
            if (pUserMem->TwainAvail) {
                EnumerateDataSources( pUserMem, GetDlgItem( hDlg, IDC_DATA_SOURCE ), GetDlgItem( hDlg, IDC_STATIC_DATA_SOURCE) );
            } else {
                pUserMem->TwainAvail = FALSE;
                return TRUE;
            }
            if (GetTempPath( sizeof(TempPath)/sizeof(WCHAR), TempPath )) {
                if (GetTempFileName( TempPath, L"fax", 0, pUserMem->FileName )) {
                    SetEnvironmentVariable( L"ScanTifName", pUserMem->FileName );
                }
            }
            pUserMem->hDlgScan = hDlg;
            return TRUE;

        case WM_NOTIFY:
            switch ( ((NMHDR *) lParam)->code) {
                case PSN_SETACTIVE:            
                    PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_NEXT);
                    if (!pUserMem->TwainAvail) {
                        //
                        // jump to next page
                        //
                        SetWindowLongPtr( hDlg, DWLP_MSGRESULT, -1 );
                        return TRUE;
                    }
                    
                    return FALSE;
                    break;
                case PSN_WIZNEXT:
                
                    if (!pUserMem->PageCount) {
                        //
                        // we didn't actually scan any pages
                        //
                        DeleteFile( pUserMem->FileName ) ;
                        SetEnvironmentVariable( L"ScanTifName", NULL );
        
                    }

                    //Scan_CloseDS( pUserMem, &pUserMem->DataSource );
                    CloseDataSource( pUserMem, &pUserMem->DataSource );
                    Assert(pUserMem->State == 3);
                    pUserMem->State = 3;

                    SetWindowLongPtr( hDlg, DWLP_MSGRESULT, IDD_WIZARD_FAXOPTS );
                    return TRUE;
                    break;
                case PSN_QUERYCANCEL:                    
                    //
                    // we didn't actually scan any pages
                    //
                    DeleteFile( pUserMem->FileName ) ;    
                    if (pUserMem->TwainActive) {
                         
                        TwUserInterface.ShowUI  = FALSE;
                        TwUserInterface.ModalUI = FALSE;
                        TwUserInterface.hParent = NULL;
                        DisableDataSource( pUserMem, &TwUserInterface );
                        
                    }

                    CloseDataSource( pUserMem, &pUserMem->DataSource );

                    break;
                default:
                    break;
            }

            break;

        case WM_PAGE_COMPLETE:
            SetDlgItemInt( hDlg, IDC_PAGE_COUNT, pUserMem->PageCount, FALSE );
            break;

        case WM_COMMAND:
            if (HIWORD(wParam) == CBN_SELCHANGE) {
                TW_IDENTITY *pDataSource;
                DWORD i;


                i = (DWORD)SendDlgItemMessage( hDlg, IDC_DATA_SOURCE, CB_GETCURSEL, 0, 0 );
                pDataSource = (TW_IDENTITY *) SendDlgItemMessage( hDlg, IDC_DATA_SOURCE, CB_GETITEMDATA, i, 0 );

                CopyMemory( &pUserMem->DataSource, pDataSource, sizeof(TW_IDENTITY) );
            }

            if (HIWORD(wParam) == BN_CLICKED) {

                //
                // scan a page
                //
                TW_USERINTERFACE  TwUserInterface;
                
                TwUserInterface.ShowUI  = TRUE;
                TwUserInterface.ModalUI = TRUE;
                TwUserInterface.hParent = GetParent(pUserMem->hDlgScan);

                if ( !pUserMem->TwainActive 
                     && Scan_OpenDS( pUserMem, &pUserMem->DataSource) 
                     //&& Scan_SetCapabilities( pUserMem )
                     && Scan_EnableDS( pUserMem, &TwUserInterface )) {
                
                    EnableWindow( GetDlgItem( hDlg, IDC_DATA_SOURCE ), FALSE );

                    hThread = CreateThread(
                        NULL,
                        0,
                        (LPTHREAD_START_ROUTINE)ScanningThread,
                        pUserMem,
                        0,
                        &ThreadId
                        );
                    if (hThread) {
                        CloseHandle( hThread );
                    }
                }                
            }
            break;

        case WM_DESTROY:
            if (pUserMem->TwainAvail) {
                TerminateTwain( pUserMem );
            }
            break;
        /* case WM_ACTIVATE:
            if (LOWORD(wParam) == WA_ACTIVE) {
                if (!pUserMem->TwainActive) {
                    EnableWindow( hDlg, TRUE );
                    return TRUE;
                }
            } */
            
    }

    return FALSE;
}

#endif


LPTSTR 
FormatTime(
    WORD Hour,
    WORD Minute,
    LPTSTR Buffer,
    DWORD BufferSize)
{
    SYSTEMTIME SystemTime;

    ZeroMemory(&SystemTime,sizeof(SystemTime));
    SystemTime.wHour = Hour;
    SystemTime.wMinute = Minute;
    GetTimeFormat(LOCALE_USER_DEFAULT,
                  TIME_NOSECONDS,
                  &SystemTime,
                  NULL,
                  Buffer,
                  BufferSize
                  );

    return Buffer;
}


INT_PTR
FinishWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the last wizard page:
    give user a chance to confirm or cancel the dialog.

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

{

    PUSERMEM    pUserMem;    
    TCHAR       RecipientNameBuffer[64];
    TCHAR       RecipientNumberBuffer[64];
    TCHAR       TmpTimeBuffer[64];
    TCHAR       TimeBuffer[64];
    TCHAR       SendTimeBuffer[64];
    TCHAR       NoneBuffer[64];
    LPTSTR      SenderName;    
    TCHAR       CoverpageBuffer[64];
    LPTSTR      Coverpage;
    HKEY        hKey;

    if (! (pUserMem = CommonWizardProc(hDlg, message, wParam, lParam, PSWIZB_BACK|PSWIZB_FINISH)) )
        return FALSE;

    switch (message) {


    case WM_NOTIFY:
        if (((NMHDR *) lParam)->code != PSN_SETACTIVE) break;
    case WM_INITDIALOG:    
        LoadString(ghInstance,IDS_NONE,NoneBuffer,sizeof(NoneBuffer)/sizeof(TCHAR) );
        
        //
        // large title font on last page
        //
        SetWindowFont(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_READY), pUserMem->hLargeFont, TRUE);
        
        //
        // set the sender name if it exists
        //
        if ( (hKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO,TRUE) ) &&
             (SenderName = GetRegistryString(hKey,REGVAL_FULLNAME,TEXT("")) )
           ) {
                SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_FROM, SenderName );
                EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_FROM),TRUE);
                EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_FROM),TRUE);
                MemFree(SenderName);
        } else {
                SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_FROM, NoneBuffer );
                EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_FROM),FALSE);
                EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_FROM),FALSE);
        }

        //
        // set the recipient name
        //
        if (pUserMem->pRecipients && pUserMem->pRecipients->pNext) {
            //
            // more than one user, just put "Multiple" in the text
            //
            LoadString(ghInstance,IDS_MULTIPLE_RECIPIENTS,RecipientNameBuffer,sizeof(RecipientNameBuffer)/sizeof(TCHAR) );
            LoadString(ghInstance,IDS_MULTIPLE_RECIPIENTS,RecipientNumberBuffer,sizeof(RecipientNumberBuffer)/sizeof(TCHAR) );
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_TO, RecipientNameBuffer );
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_NUMBER, RecipientNumberBuffer );
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_TO),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_TO),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_NUMBER),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_NUMBER),FALSE);
        } else {
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_TO, pUserMem->pRecipients->pName );
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_NUMBER, pUserMem->pRecipients->pAddress );
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_TO),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_TO),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_NUMBER),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_NUMBER),TRUE);
        }

        //
        // when to send
        //
        switch (pUserMem->devmode.dmPrivate.whenToSend) {
        case SENDFAX_AT_TIME:
            LoadString(ghInstance,IDS_SEND_SPECIFIC,TmpTimeBuffer,sizeof(TmpTimeBuffer)/sizeof(TCHAR) );
            wsprintf(SendTimeBuffer,
                     TmpTimeBuffer,
                     FormatTime(pUserMem->devmode.dmPrivate.sendAtTime.Hour,
                                pUserMem->devmode.dmPrivate.sendAtTime.Minute,
                                TimeBuffer,
                                sizeof(TimeBuffer)) );
            break;
        case SENDFAX_AT_CHEAP:
            LoadString(ghInstance,IDS_SEND_DISCOUNT,SendTimeBuffer,sizeof(SendTimeBuffer)/sizeof(TCHAR) );
            break;
        case SENDFAX_ASAP:
            LoadString(ghInstance,IDS_SEND_ASAP,SendTimeBuffer,sizeof(SendTimeBuffer)/sizeof(TCHAR) );
        };
        
        SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_TIME, SendTimeBuffer );

        //
        // Coverpage
        //
        if (pUserMem->devmode.dmPrivate.sendCoverPage) {
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_COVERPG),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_SUBJECT),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_COVERPG),TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_SUBJECT),TRUE);
            
            //
            // format the coverpage for display to the user
            //

            // drop path
            Coverpage = _tcsrchr(pUserMem->coverPage,TEXT(PATH_SEPARATOR));
            if (!Coverpage) {
                Coverpage = pUserMem->coverPage;
            } else {
                Coverpage++;
            }
            _tcscpy(CoverpageBuffer,Coverpage);

            // crop file extension
            Coverpage = _tcschr(CoverpageBuffer,TEXT(FILENAME_EXT));
            
            if (Coverpage && *Coverpage) {
                *Coverpage = (TCHAR) NUL;
            }

            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_COVERPG, CoverpageBuffer );
            if (pUserMem->pSubject) {
                SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_SUBJECT, pUserMem->pSubject ); 
            } else {
                EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_SUBJECT),FALSE);
                EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_SUBJECT),FALSE);
                SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_SUBJECT, NoneBuffer );
            }            
        } else {
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_COVERPG, NoneBuffer );
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_SUBJECT, NoneBuffer );

            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_COVERPG),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_SUBJECT),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_COVERPG),FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_SUBJECT),FALSE);
        }

        //
        // Billing Code
        //
        if (pUserMem->devmode.dmPrivate.billingCode[0]) {
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_BILLING), TRUE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_BILLING),TRUE);
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_BILLING, pUserMem->devmode.dmPrivate.billingCode );
        } else {
            EnableWindow(GetDlgItem(hDlg,IDC_STATIC_WIZ_CONGRATS_BILLING), FALSE);
            EnableWindow(GetDlgItem(hDlg,IDC_WIZ_CONGRATS_BILLING),        FALSE);
            SetDlgItemText(hDlg, IDC_WIZ_CONGRATS_BILLING, NoneBuffer );
        }

        return TRUE;

    default:
        return FALSE;        
    } ;

    return TRUE;
    
}

INT_PTR
WelcomeWizProc(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )

/*++

Routine Description:

    Dialog procedure for the last wizard page:
    give user a chance to confirm or cancel the dialog.

Arguments:

    hDlg - Identifies the wizard page
    message - Specifies the message
    wParam - Specifies additional message-specific information
    lParam - Specifies additional message-specific information

Return Value:

    Depends on the message parameter

--*/

{
    
    PUSERMEM    pUserMem;    

    if (! (pUserMem = CommonWizardProc(hDlg, message, wParam, lParam, PSWIZB_NEXT)))
        return FALSE;

    switch (message) {

    case WM_INITDIALOG:
        //
        // set the large fonts
        //
        SetWindowFont(GetDlgItem(hDlg,IDC_WIZ_WELCOME_TITLE), pUserMem->hLargeFont, TRUE);        

        //
        // show this text only if we're running the send wizard
        //
        if (!GetEnvironmentVariable(TEXT("NTFaxSendNote"), NULL, 0)) {
            MyHideWindow(GetDlgItem(hDlg,IDC_WIZ_WELCOME_FAXSEND) );
            MyHideWindow(GetDlgItem(hDlg,IDC_WIZ_WELCOME_FAXSEND_CONT) );
        }

        return TRUE;
    } ;

    return FALSE;
}



BOOL
GetFakeRecipientInfo(
    PUSERMEM    pUserMem,
    DWORD       nRecipients
    )

/*++

Routine Description:

    Skip send fax wizard and get faked recipient information from the registry

Arguments:

    pUserMem - Points to the user mode memory structure
    nRecipients - Total number of faked recipient entries

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    LPTSTR  pRecipientEntry;
    DWORD   index;
    TCHAR   buffer[MAX_STRING_LEN];
    BOOL    success = FALSE;
    HKEY    hRegKey;

    //
    // Retrieve information about the next fake recipient entry
    //

    Verbose(("Send Fax Wizard skipped...\n"));

    if (hRegKey = GetUserInfoRegKey(REGKEY_FAX_USERINFO, REG_READWRITE))
        index = GetRegistryDword(hRegKey, REGVAL_STRESS_INDEX);
    else
        index = 0;

    if (index >= nRecipients)
        index = 0;

    wsprintf(buffer, TEXT("FakeRecipient%d"), index);
    pRecipientEntry = GetPrinterDataStr(pUserMem->hPrinter, buffer);

    if (hRegKey) {

        //
        // Update an index so that next time around we'll pick a different fake recipient
        //

        if (++index >= nRecipients)
            index = 0;

        SetRegistryDword(hRegKey, REGVAL_STRESS_INDEX, index);
        RegCloseKey(hRegKey);
    }

    //
    // Each fake recipient entry is a REG_MULTI_SZ of the following format:
    //  recipient name #1
    //  recipient fax number #1
    //  recipient name #2
    //  recipient fax number #2
    //  ...
    //

    if (pRecipientEntry) {

        __try {

            PRECIPIENT  pRecipient;
            LPTSTR      pName, pAddress, p = pRecipientEntry;

            while (*p) {

                pName = p;
                pAddress = pName + _tcslen(pName) + 1;
                p = pAddress + _tcslen(pAddress) + 1;

                pRecipient = MemAllocZ(sizeof(RECIPIENT));
                pName = DuplicateString(pName);

                pAddress = DuplicateString(pAddress);

                if (!pRecipient || !pName || !pAddress) {

                    Error(("Invalid fake recipient information\n"));
                    MemFree(pRecipient);
                    MemFree(pName);
                    MemFree(pAddress);
                    break;
                }

                pRecipient->pNext = pUserMem->pRecipients;
                pUserMem->pRecipients = pRecipient;
                pRecipient->pName = pName;
                pRecipient->pAddress = pAddress;
            }

        } __finally {

            if (success = (pUserMem->pRecipients != NULL)) {

                //
                // Determine whether a cover page should be used
                //

                LPTSTR  pCoverPage;

                pCoverPage = GetPrinterDataStr(pUserMem->hPrinter, TEXT("FakeCoverPage"));

                if (pUserMem->devmode.dmPrivate.sendCoverPage = (pCoverPage != NULL))
                    CopyString(pUserMem->coverPage, pCoverPage, MAX_PATH);

                MemFree(pCoverPage);
            }
        }
    }

    MemFree(pRecipientEntry);
    return success;
}



BOOL
SendFaxWizard(
    PUSERMEM    pUserMem
    )

/*++

Routine Description:

    Present the Send Fax Wizard to the user. This is invoked
    during CREATEDCPRE document event.

Arguments:

    pUserMem - Points to the user mode memory structure

Return Value:

    TRUE if successful, FALSE if there is an error or the user pressed Cancel.

--*/
#ifdef FAX_SCAN_ENABLED
    #define NUM_PAGES   6   // Number of wizard pages
#else
    #define NUM_PAGES   5   // Number of wizard pages
#endif

{
    PROPSHEETPAGE  *ppsp;
    PROPSHEETHEADER psh;
    INT             result;
    DWORD           nRecipients;
    HDC             hdc;
    int             i;
    LOGFONT         LargeFont;
    NONCLIENTMETRICS ncm = {0};
    TCHAR           FontName[100];
    TCHAR           FontSize[30];
    int             iFontSize;
    DWORD           ThreadId;
    HANDLE          hThread;
    

    Assert(pUserMem->pRecipients == NULL);

    //
    // A shortcut to skip fax wizard for debugging/testing purposes
    //

    if (nRecipients = GetPrinterDataDWord(pUserMem->hPrinter, TEXT("FakeRecipientCount"), 0))
        return GetFakeRecipientInfo(pUserMem, nRecipients);

    Verbose(("Presenting Send Fax Wizard...\n"));

    if (! (ppsp = MemAllocZ(sizeof(PROPSHEETPAGE) * NUM_PAGES))) {

        Error(("Memory allocation failed\n"));
        return FALSE;
    }

    //
    // fire off a thread to do some slow stuff later on in the wizard.
    //
    pUserMem->hFaxSvcEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    pUserMem->hTapiEvent   = CreateEvent(NULL,FALSE,FALSE,NULL);
#ifdef FAX_SCAN_ENABLED
    pUserMem->hTwainEvent  = CreateEvent(NULL,FALSE,FALSE,NULL);

    if (!pUserMem->hFaxSvcEvent || !pUserMem->hTapiEvent || !pUserMem->hTwainEvent) {
       Error(("CreateEvent for async events failed\n"));
       return FALSE;
    }
#else
    if (!pUserMem->hFaxSvcEvent || !pUserMem->hTapiEvent) {
       Error(("CreateEvent for async events failed\n"));
       return FALSE;
    }
#endif
    


    hThread = CreateThread(NULL,0,AsyncWizardThread,pUserMem,0,&ThreadId);
    if (hThread) {
       CloseHandle(hThread);
    } else {
       return FALSE;
    }

    //
    // Fill out one PROPSHEETPAGE structure for every page:
    //  The first page is a welcome page
    //  The first page is for choose the fax recipient
    //  The second page is for choosing cover page, subject and note
    //  The third page is for entering time to send and other options
    //  The fourth page is for scanning pages
    //  The last page gives the user a chance to confirm or cancel the dialog
    //

    FillInPropertyPage( ppsp,   IDD_WIZARD_WELCOME,    WelcomeWizProc,    pUserMem ,0,0);
    FillInPropertyPage( ppsp+1, IDD_WIZARD_CHOOSE_WHO, RecipientWizProc,  pUserMem ,IDS_WIZ_RECIPIENT_TITLE,IDS_WIZ_RECIPIENT_SUB);

    //
    // set second page title correctly if we're running as faxsend or from file print
    if (!GetEnvironmentVariable(TEXT("NTFaxSendNote"), NULL, 0)) {
        FillInPropertyPage( ppsp+2, IDD_WIZARD_CHOOSE_CP,  CoverPageWizProc,  pUserMem ,IDS_WIZ_COVERPAGE_TITLE_2,IDS_WIZ_COVERPAGE_SUB_2 );
    }
    else {
        FillInPropertyPage( ppsp+2, IDD_WIZARD_CHOOSE_CP,  CoverPageWizProc,  pUserMem ,IDS_WIZ_COVERPAGE_TITLE_1,IDS_WIZ_COVERPAGE_SUB_1 );
    }


#ifdef FAX_SCAN_ENABLED
    FillInPropertyPage( ppsp+3, IDD_WIZARD_SCAN,       ScanWizProc,       pUserMem ,IDS_WIZ_SCAN_TITLE,IDS_WIZ_SCAN_SUB);
    FillInPropertyPage( ppsp+4, IDD_WIZARD_FAXOPTS,    FaxOptsWizProc,    pUserMem ,IDS_WIZ_FAXOPTS_TITLE,IDS_WIZ_FAXOPTS_SUB);
    FillInPropertyPage( ppsp+5, IDD_WIZARD_CONGRATS,   FinishWizProc,     pUserMem ,0,0);
#else
    FillInPropertyPage( ppsp+3, IDD_WIZARD_FAXOPTS,    FaxOptsWizProc,    pUserMem ,IDS_WIZ_FAXOPTS_TITLE,IDS_WIZ_FAXOPTS_SUB);
    FillInPropertyPage( ppsp+4, IDD_WIZARD_CONGRATS,   FinishWizProc,     pUserMem ,0,0);
#endif

    //
    // Fill out the PROPSHEETHEADER structure
    //

    ZeroMemory(&psh, sizeof(psh));

    psh.dwSize = sizeof(PROPSHEETHEADER);
    psh.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD | PSH_WIZARD97 | PSH_WATERMARK | PSH_HEADER;
    psh.hwndParent = GetActiveWindow();
    psh.hInstance = ghInstance;
    psh.hIcon = NULL;
    psh.pszCaption = TEXT("");
    psh.nPages = NUM_PAGES;
    psh.nStartPage = 0;
    psh.ppsp = ppsp;
    psh.pszbmHeader = MAKEINTRESOURCE(IDB_FAXWIZ_WATERMARK);
    psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK_16);

    if(hdc = GetDC(NULL)) {
        if(GetDeviceCaps(hdc,BITSPIXEL) >= 8) {
            psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK_256);
        }
        ReleaseDC(NULL,hdc);
    }

    
    //
    // get the large fonts for wizard97
    // 
    ncm.cbSize = sizeof(ncm);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);

    CopyMemory((LPVOID* )&LargeFont,(LPVOID *) &ncm.lfMessageFont,sizeof(LargeFont) );

    
    LoadString(ghInstance,IDS_LARGEFONT_NAME,FontName,sizeof(FontName)/sizeof(TCHAR) );
    LoadString(ghInstance,IDS_LARGEFONT_SIZE,FontSize,sizeof(FontSize)/sizeof(TCHAR) );

    iFontSize = _tcstoul( FontSize, NULL, 10 );

    // make sure we at least have some basic font
    if (*FontName == 0 || iFontSize == 0) {
        lstrcpy(FontName,TEXT("MS Shell Dlg") );
        iFontSize = 18;
    }

    lstrcpy(LargeFont.lfFaceName, FontName);        
    LargeFont.lfWeight   = FW_BOLD;

    if (hdc = GetDC(NULL)) {
        LargeFont.lfHeight = 0 - (GetDeviceCaps(hdc,LOGPIXELSY) * iFontSize / 72);
        pUserMem->hLargeFont = CreateFontIndirect(&LargeFont);
        ReleaseDC( NULL, hdc);
    }


    //
    // Display the wizard pages
    //    
    if (PropertySheet(&psh) > 0)
        result = pUserMem->finishPressed;
    else
        result = FALSE;

    //
    // Cleanup properly before exiting
    //

    //
    // free headings
    //    
    for (i = 0; i< NUM_PAGES; i++) {
        MemFree( (PVOID)(ppsp+i)->pszHeaderTitle );
        MemFree( (PVOID)(ppsp+i)->pszHeaderSubTitle );
    }

    if (pUserMem->lpWabInit) {

        UnInitializeWAB( pUserMem->lpWabInit);
    }

    DeinitTapiService();
    MemFree(ppsp);

    DeleteObject(pUserMem->hLargeFont);

    FreeCoverPageInfo(pUserMem->pCPInfo);
    pUserMem->pCPInfo = NULL;

#ifdef FAX_SCAN_ENABLED
    //
    // if the user pressed cancel, cleanup leftover scanned file.
    //
    if (!pUserMem->finishPressed && pUserMem->TwainAvail && pUserMem->FileName) {
        DeleteFile( pUserMem->FileName ) ;
        SetEnvironmentVariable( L"ScanTifName", NULL );        
    }
#endif

    Verbose(("Wizard finished...\n"));
    return result;
}