//
//  CHAPT15.CPP
//
//  Source code from:
//
//  Serial Communications: A C++ Developer's Guide, 2nd Edition
//  by Mark Nelson, IDG Books, 1999
//
//  Please see the book for information on usage.
//
// This file contains the source code for the Chapter 15
// demo program. To build this file, use the Visual C++
// project file included in the same directory as the 
// sample code. CHAPT15.CPP shows how you might use TAPI
// to take care of modem management in a communications
// program. The terminal emulation portion of this program
// is essentially identical to that of Chapter 13. Where
// it differs is in the use of TAPI to set up the connection
// to a distant modem.
//

#include <windows.h>
#include "resource.h"

#include "AnsiTapiTerm.h"
#include "MySimpleTapi.h"

//
// A couple of forward references needed throughout the
// program.
//

LRESULT CALLBACK WinProc( HWND hwnd, 
                          UINT message, 
                          WPARAM wParam, 
                          LPARAM lParam );
BOOL CALLBACK DlgProc( HWND hWnd,
                       UINT uMsg,
                       WPARAM wParam,
                       LPARAM lParam );

//
// The WinMain() function for this program looks
// very generic. It has to register the class we
// will create for this demo program, being sure
// to include our menu and WinProc. Once the class
// is registered, we can just create the window and
// let it do all the rest of the work. From that
// point on, all we have to do in this routine is
// run the message loop until somebody decides it's
// time to exit.
//

int WINAPI WinMain( HINSTANCE hInstance, 
                    HINSTANCE /* hPrevInstance */, 
                    LPSTR     /* lpCmdLine */, 
                    int       nShowCmd )
{
    //
    // Note that we store enough space extra in our class
    // to hold two pointers. The two pointers that will
    // be stored in the extra storage space are a pointer to
    // the Terminal Emulation object and another to the
    // Tapi object. 
    //
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc      = WinProc;
    wc.hInstance        = hInstance;
    wc.lpszMenuName     = MAKEINTRESOURCE( IDR_MENU );
    wc.hbrBackground    = (HBRUSH) ( COLOR_WINDOW + 1 );
    wc.lpszClassName    = "Chapter15Class";
    wc.cbWndExtra       = sizeof( Win32Term * ) + sizeof( MySimpleTapi * );
    if ( !RegisterClass( &wc ) ) {
        MessageBox( NULL, "Could not register Chapter 15 class!", NULL, MB_OK );
        return 0;
    }
    HWND hwnd = CreateWindow( "Chapter15Class",
                              "Chapter 15 Test Program",
                              WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                              CW_USEDEFAULT, 
                              CW_USEDEFAULT,
                              647, // hand picked size to fit exactly 25 x 80
                              347,
                              NULL, 
                              NULL, 
                              hInstance, 
                              NULL );

    if ( hwnd == NULL ) {
        MessageBox( NULL, "Chapter15.exe couldn't start!", NULL, MB_OK );
        return 0;
    }
    ShowWindow( hwnd, nShowCmd );
    UpdateWindow( hwnd );

    MSG msg;
    while ( GetMessage( &msg, NULL, 0, 0 ) ) 
    {
        TranslateMessage( &msg ) ;
        DispatchMessage( &msg ) ;
    }
    return 1;
}

//
// To make the user interface a bit easier to fololow, the
// Tapi menu has quite a few entries that can be enabled or
// disabled, and in many cases have two different meanings.
// For example, the menu entry ID_TAPI_PLACE_CALL is set
// to have a string of "Place Call" when the there is no
// active call up, and "Drop Call" when there is an active
// call. Likewise, the menu item is grayed/disabled when
// the TAPI line is closed, and enabled when the TAPI line
// is open.
//
// This routine is called to fix up all the various TAPI
// menu options whenever anything happens that might require
// a change. Clearly it is a bit of overkill to redo all the
// menu options when maybe just one thing changes, but it
// is easier to do it this way than attempting to be clever.
//

void UpdateMenu( HWND hwnd,
                 MySimpleTapi *pTapi )
{
    HMENU menu = GetSubMenu( GetMenu( hwnd ), 1 );
    //
    // The Open Trace Window item is always enabled, we 
    // only have to determine what the appropriate text
    // should be, based on the state of the trace window.
    //
    if ( SimpleTapi::m_Trace.IsOpen() )
        ModifyMenu( menu, 
                    ID_TAPI_OPEN_TRACE_WINDOW, 
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_OPEN_TRACE_WINDOW, 
                    "Close Trace Window" );
    else
        ModifyMenu( menu, 
                    ID_TAPI_OPEN_TRACE_WINDOW, 
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_OPEN_TRACE_WINDOW, 
                    "Open Trace Window" );
    //
    // Likewise, the second menu item, Create TAPI Object,
    // will always be enabled. The menu text will be set
    // to either Create or Destroy the TAPI object.
    //
    if ( pTapi )
        ModifyMenu( menu, 
                    ID_TAPI_CREATE_TAPI_OBJECT, 
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_CREATE_TAPI_OBJECT, 
                    "Destroy TAPI Object" );
    else
        ModifyMenu( menu, 
                    ID_TAPI_CREATE_TAPI_OBJECT, 
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_CREATE_TAPI_OBJECT, 
                    "Create TAPI Object" );
    //
    // The Open Line menu item should be disabled unless
    // a TAPI object exists. If a TAPI object exists,
    // the text will depend strictly on whether a line
    // is open or not.
    //
    if ( pTapi && pTapi->IsOpen() )
        ModifyMenu( menu,
                    ID_TAPI_OPEN_LINE,
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_OPEN_LINE,
                    "Close Line" );
    else
        ModifyMenu( menu,
                    ID_TAPI_OPEN_LINE,
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_OPEN_LINE,
                    "Open Line" );
    if ( pTapi )
        EnableMenuItem( menu,
                        ID_TAPI_OPEN_LINE,
                        MF_BYCOMMAND | MF_ENABLED );
    else
        EnableMenuItem( menu,
                        ID_TAPI_OPEN_LINE,
                        MF_BYCOMMAND | MF_GRAYED );
    //
    // The Place Call menu item has text that changes
    // back and forth between Place Call and Drop Call,
    // depending on whether a call is active. It should
    // only be enabled if a line is open.
    //
    if ( pTapi && pTapi->IsOpen() && pTapi->CallActive() ) 
        ModifyMenu( menu,
                    ID_TAPI_PLACE_CALL,
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_PLACE_CALL,
                    "Drop Call" );
    else
        ModifyMenu( menu,
                    ID_TAPI_PLACE_CALL,
                    MF_BYCOMMAND | MF_STRING,
                    ID_TAPI_PLACE_CALL,
                    "Place Call" );
    if ( pTapi && pTapi->IsOpen() )
        EnableMenuItem( menu,
                        ID_TAPI_PLACE_CALL,
                        MF_BYCOMMAND | MF_ENABLED );
    else
        EnableMenuItem( menu,
                        ID_TAPI_PLACE_CALL,
                        MF_BYCOMMAND | MF_GRAYED );
    //
    // The three configuration menu items are all enabled
    // if the TAPI object is alive, disabled if not. Their
    // text never changes.
    //
    if ( pTapi )
        EnableMenuItem( menu,
                        ID_TAPI_CONFIGURE_LINE,
                        MF_BYCOMMAND | MF_ENABLED );
    else
        EnableMenuItem( menu,
                        ID_TAPI_CONFIGURE_LINE,
                        MF_BYCOMMAND | MF_GRAYED );
    if ( pTapi )
        EnableMenuItem( menu,
                        ID_TAPI_CONFIGURE_CALL,
                        MF_BYCOMMAND | MF_ENABLED );
    else
        EnableMenuItem( menu,
                        ID_TAPI_CONFIGURE_CALL,
                        MF_BYCOMMAND | MF_GRAYED );
    if ( pTapi )
        EnableMenuItem( menu,
                        ID_TAPI_SET_PHONE_NUMBER,
                        MF_BYCOMMAND | MF_ENABLED );
    else
        EnableMenuItem( menu,
                        ID_TAPI_SET_PHONE_NUMBER,
                        MF_BYCOMMAND | MF_GRAYED );
}


//
// The WinProc given here starts with the same core message
// handlers that were seen in Chapter 13. The main window has 
// a single child window that acts as a terminal emulator when
// the port is open. The message handlers for WM_CREATE, 
// WM_DESTROY, WM_GETMINMAXINFO, WM_SIZE, and WM_SETFOCUS
// all deal with issues related to that child window. I 
// stripped the commands that dealt with the fonts, colors, 
// and so on for the child window, but they could easily be
// restored with no more than cut and paste operations.
//
// The remaining message handlers for this window all have 
// to deal with issues related to TAPI. All but two of the
// handlers are called as a result of making selections from
// the main menu, and are under the WM_COMMAND handler. The
// other two are messages sent from the TAPI notification
// routines. 
//
// Details on exactly what these message handlers do are 
// found inline with the code. In general, the way to use
// this program is to:
//
//  1) Open the trace window for help with debugging.
//  2) Create a TAPI object.
//  3) Open a Line
//  4) Wait for an incoming call
//   -- or --
//  4) Set the outbound phone number
//  5) Place a call
//
//  Either way, the goal at this point is to wait for the 
//  modem to connect to another modem. When that happens, 
//  the port is opened, and you are then operating a 
//  terminal emulator.
//
LRESULT CALLBACK WinProc( HWND hWnd, 
                          UINT message, 
                          WPARAM wParam, 
                          LPARAM lParam )
{
    //
    // These two pointers reference objects that are stored 
    // in the excess storage area for this window. The 
    // pointers are used far and wide among various message
    // handlers, so we set them up here for anyone who wants
    // to use them. Note that once the window has been 
    // created, pTerm should always point to a valid terminal
    // emulator window, but pTapi may or may not be null
    //
    AnsiTapiTerm *pTerm;
    MySimpleTapi *pTapi;
    pTerm = (AnsiTapiTerm *) GetWindowLong( hWnd, 0 );
    pTapi = (MySimpleTapi *) GetWindowLong( hWnd, 4 );

    switch ( message ) 
    {
        //
        // When my window is first created, I immediately
        // create the terminal window as a child window. I
        // get the pointer to the C++ object and store it as
        // a window long word, giving me access to it whenever
        // I need it without using a global variable. 
        //
        case WM_CREATE:
            pTerm = new AnsiTapiTerm( hWnd, "AnsiTerm Window", 25, 80 );
            SetWindowLong( hWnd, 0, (LONG) pTerm );
            if ( pTerm->m_hWnd == NULL )
                MessageBox( hWnd, "Can't open child window", "Chapter 15", MB_OK );
            SetFocus( hWnd );
            break;
        //
        // When I am being destroyed, I take it upon myself 
        // to destroy both the terminal object and the Tapi 
        // object, if one exists.
        //
        case WM_DESTROY: 
            delete pTerm;
            pTerm = 0;
            if ( pTapi ) {
                delete pTapi;
                pTapi = 0;
            }
            PostQuitMessage( 0 );
            break;
        //
        // Just for the sake of esthetics, I respond to this message
        // with a minimum size large enough to prevent somebody from
        // resizing me down to the point of ridiculousness.
        //
        case WM_GETMINMAXINFO:
            {
                LPMINMAXINFO lp = (LPMINMAXINFO) lParam;
                POINT ptTemp = { 
                    lp->ptMinTrackSize.x,
                    GetSystemMetrics( SM_CYMENU )        + 
                       GetSystemMetrics( SM_CYCAPTION )  +
                       2 * GetSystemMetrics( SM_CYFRAME )
                };
                lp->ptMinTrackSize = ptTemp;
            }        
            break;

        //
        // When the frame window is resized, I immediately 
        // resize the terminal window. Size the terminal window
        // is supposed to completely fill my client area, I
        // can figure out what size it is supposed to be by
        // simply getting the size of my client rect. When
        // the terminal window processes this command it will
        // potentially add scroll bars and offset the display.
        //
        case WM_SIZE:
            {
                RECT rc;
                ::GetClientRect( hWnd, &rc );
                if ( lParam != 0 )
                ::MoveWindow( pTerm->m_hWnd, 
                              0, 0, 
                              rc.right - rc.left + 1,
                              rc.bottom - rc.top + 1,
                              TRUE );
            }
            break;
        //
        // I don't want the framing window to get the focus,
        // so whenever I get it, I immediately foist it upon
        // the terminal window.
        //
        case WM_SETFOCUS :
            SetFocus( pTerm->m_hWnd );
            return 0;
        //
        // The rest of the code in the message loop 
        // is the set of command handlers for menu
        // items.
        //
        case WM_COMMAND:       
            switch ( LOWORD( wParam ) ) {
            //
            // Telling a window to close itself is a fast way 
            // of shutting the program down, so that's how
            // we deal with the File|Exit menu item.
            //
            case ID_FILE_EXIT :
                PostMessage( hWnd, WM_CLOSE, 0, 0 );
                break;
            //
            // The base class, SimpleTapi, has a trace object
            // that receives various messages as the object
            // does its TAPI stuff. This trace window can be
            // open or closed. This command toggles it back
            // and forth between these two states. Since the
            // trace object is static, we don't even need to
            // use a pointer to the TAPI object. When the 
            // object is opened, a completely separate 
            // console window is opened up to receive trace
            // information.
            //
            case ID_TAPI_OPEN_TRACE_WINDOW :
                if ( SimpleTapi::m_Trace.IsOpen() ) 
                    SimpleTapi::m_Trace.Close();
                else 
                    SimpleTapi::m_Trace.Open();
                UpdateMenu( hWnd, pTapi );
                break;
            //
            // We can't do much of anything interesting
            // with this program until the simple TAPI 
            // object is created. This command handler
            // will either create or destroy the object,
            // depending on whether or not it currently
            // exists. The value of the pTapi after the
            // operation has to be stuffed in the window
            // storage area so it will persist past the 
            // end of this command processing.
            //
            case ID_TAPI_CREATE_TAPI_OBJECT :
                if ( pTapi ) {
                    delete pTapi;
                    pTapi = 0;
                } else 
                    pTapi = new MySimpleTapi( hWnd );
                SetWindowLong( hWnd, 4, (LONG) pTapi );
                UpdateMenu( hWnd, pTapi );
                break;
            //
            // This menu object should be grayed out unless
            // the pTapi object has been created. If it has,
            // we can either open or close the line. Note 
            // that this call is hard coded to open the first
            // TAPI line. A better program would let you
            // select from among the available lines via
            // some sort of selection dialog. Note that the
            // two functions called here both return 
            // immediately, we don't have to wait for a
            // delayed response.
            //
            case ID_TAPI_OPEN_LINE :
                if ( pTapi->IsOpen() ) 
                    pTapi->CloseLine();
                else 
                    pTapi->OpenLine( 0 );
                UpdateMenu( hWnd, pTapi );
                break;
            //
            // This menu item should only be enabled if a
            // line has been opened. It will then either 
            // place a call or drop a call, depending on
            // whether a call is active. This is a little
            // bit tricky, because we have to deal with the
            // fact that both of these calls don't return
            // an immediate result
            //
            case ID_TAPI_PLACE_CALL :
                if ( pTapi->CallActive() )
                    pTapi->DropCall();
                else
                    pTapi->MakeCall( pTapi->m_NumberToDial );
                break;
            //
            // The followign three menu items are enabled as
            // long as a TAPI object is alive. All three are
            // used to configure the parameters surrounding 
            // a TAPI call. The first two just invoke the 
            // standard dialogs supplied by TAPI to configure
            // modems and the way they place calls. The final
            // of the three pops up a dialog that optionally
            // modifies the current outbound phone number.
            //
            case ID_TAPI_CONFIGURE_LINE :
                pTapi->ConfigureDevice( 0 );
                break;
            case ID_TAPI_CONFIGURE_CALL :
                pTapi->ConfigureCall( 0, pTapi->m_NumberToDial );
                break;
            case ID_TAPI_SET_PHONE_NUMBER :
                DialogBox( GetModuleHandle( NULL ), 
                           MAKEINTRESOURCE(IDD_GET_PHONE_NUMBER),
                           hWnd, 
                           (DLGPROC) DlgProc );
                break;

            }
            break;
        //
        // My specialized class derived from SimpleTapi
        // is called MySimpleTapi. I've configured it to
        // send the following Windows messages to a specific
        // window as part of its notification process. I use
        // the notification event to update the UI, mostly
        // by printing a message on the terminal screen and
        // 
        case WM_TAPI_CONNECTED_NOTIFY :
            *pTerm << "\r\nConnected\r\n";
            UpdateMenu( hWnd, pTapi );
            break;
        case WM_TAPI_DISCONNECTED_NOTIFY :
            *pTerm << "\r\nDisconnected\r\n";
            UpdateMenu( hWnd, pTapi );
            break;
        case WM_TAPI_STATE_CHANGE_NOTIFY :
            UpdateMenu( hWnd, pTapi );
            break;
        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }
    return 0L;
}

//
// This program was written without the use of MFC or any
// other application framework, which means that using a
// dialog box to get a text string is a bit of trouble. The
// sole use of a dialog box in this program is to get the 
// phone number that is to be dialed upon selection of the
// Place Call menu item.
//
// This dialog box assumes that its parent is the Chapter 15
// main window. Because of that, it can extract a pointer to 
// the TAPI object from the storage area associated with that
// window. The phone number is stored in that structure, 
// which means we can preload the text box with the number, 
// then update the structure with the new value if the user
// clicks on the OK box to exit.
//

BOOL CALLBACK DlgProc( HWND hWnd,
                       UINT uMsg,
                       WPARAM wParam,
                       LPARAM lParam )
{
    HWND hParent = GetParent( hWnd );
    MySimpleTapi *pTapi;
    pTapi = (MySimpleTapi *) GetWindowLong( hParent, 4 );
  
    switch ( uMsg ) {

    //
    // When the dialog is first being created, we load up
    // the edit text box with a copy of the phone number
    // currently loaded into the text box.
    //
    case WM_INITDIALOG :
        SetDlgItemText( hWnd, 
                        IDC_PHONE_NUMBER,
                        pTapi->m_NumberToDial.c_str() );
        return TRUE;
    
    case WM_COMMAND :
        switch ( LOWORD( wParam ) ) {
        //
        // If the user clicks on the cancel button, we just
        // exit without updating any data.
        //
        case IDCANCEL :
            EndDialog( hWnd, 0 );
            return TRUE;
        //
        // If the user clicks OK, we update the phone number
        // field in the TAPI object with whatever was typed
        // into the field. No checks are made to ensure that
        // it is a legal number.
        //
        case IDOK :
            {
                char number[ 25 ];
                ::GetDlgItemText( hWnd,
                                  IDC_PHONE_NUMBER,
                                  number,
                                  24 );
                pTapi->m_NumberToDial = number;
                EndDialog( hWnd, 0 );
            }
            return TRUE;
        }
    }
    return FALSE;
}

// End of CHAPT15.CPP
