/*++

Copyright (c) 1995 Intel Corp

Module Name:

    ws2chat.c

Abstract:

    Contains WinMain and other user interface code for the WinSock2
    Chat sample application.

--*/


#include "nowarn.h"  /* turn off benign warnings */
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */
#endif
#include <windows.h>
#include "nowarn.h"  /* some warnings may have been turned back on */
#include <winsock2.h>
#include <stdlib.h>
#include "ws2chat.h"
#include "chatsock.h"
#include "chatdlg.h"
#include "queue.h"


//
// Forward References -- internal functions.
//

// Startup/Initialization Functions
int PASCAL
WinMain(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle,
    IN LPSTR  CmdLine,
    IN int    CmdShow);

BOOL
InitUI(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle);

long CALLBACK
MainWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam);

long CALLBACK
ConnWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam);

long CALLBACK
SendEditSubClass(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam);

long CALLBACK
RecvEditSubClass(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam);

BOOL CALLBACK
CloseEnumProc(
    IN HWND WindowHandle,
    IN LONG LParam);

BOOL
GetConnectionInfo(
    IN HWND ConnectionWindow);

void
QueryAndMaximize(void);


//
// Static Global Variables
//

static HMENU   MainMenu;       // the main MDI menu
static HMENU   MainMenuWindow; // the window associated with the above
static FARPROC OldEditWndProc; // edit control WndProc before subclassing



// 
// Externally-Visible Global Variables
//

HANDLE  GlobalInstance;                  // ientifies the instance of chat
char    ConnClassStr[] = "ConnChild";    // string to register window class
char    ChatClassStr[] = "WS2ChatFrame"; // string to register window class
HWND    GlobalFrameWindow;               // Chat's main (frame) window



//
// Function Definitions
//


int CALLBACK
WinMain(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle,
    IN LPSTR  CmdLine,
    IN int    CmdShow)

/*++

Routine Description:

    WinMain is the main entry point for WinSock 2 Chat.  This function
    calls initialization functions and goes into a message loop.

Arguments:

    InstanceHandle -- Supplies the current instance handle.

    PrevInstanceHandle -- Supplies the previous instance handle.

    CmdLine -- Supplies the address of the command line.

    CmdShow -- Supplies the show state of the window.

Return Value:

    0 -- Initialization failure

    !0 -- Returns the Message.wParam of the WM_QUIT message that ended
    the message loop.

--*/
{
    HANDLE   AccelTable;   // handle to the accelerator table
    HWND     ClientWindow; // MDI Client window
    MSG      Message;      // holds the message
    int      ReturnValue;  // return value

    ReturnValue = 0;
    GlobalInstance = InstanceHandle;

    // Initialize Chat.
    if (!InitUI(InstanceHandle, PrevInstanceHandle)) {
        MessageBox(NULL, "Couldn't Initialize Chat!", "Error", 
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        goto Done;
    }

    // Create the frame window.
    GlobalFrameWindow = CreateWindow(ChatClassStr,
                                     "WinSock 2 Chat",
                                     WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                     CW_USEDEFAULT,
                                     CW_USEDEFAULT,
                                     CW_USEDEFAULT,
                                     CW_USEDEFAULT,
                                     NULL,
                                     (HMENU)MainMenu,
                                     InstanceHandle,
                                     NULL);
    
    if (GlobalFrameWindow == NULL) {
        ChatSysError("CreateWindow()", 
                     "WinMain()", 
                     TRUE);
    }
    
    // Startup WinSock 2.
    if (!InitWS2()) {
        goto Done;
    }

    // Find out about protocols installed.
    if (!FindProtocols()) {
        goto Done;
    }

    // Listen on a local address for all installed protocols.
    if (!ListenAll()) {
        goto Done;
    }
    
    // Load some handles, update the application window, and enter the
    // main message loop.
    ClientWindow = GetWindow(GlobalFrameWindow, GW_CHILD);
    AccelTable = LoadAccelerators(InstanceHandle, "MdiAccel");
    if (AccelTable == NULL) {
        ChatSysError("LoadAccelerators()",
                     "WinMain()",
                     FALSE);
    }
    ShowWindow(GlobalFrameWindow, CmdShow);
    UpdateWindow(GlobalFrameWindow);

    // Enter the GetMessage loop.
    while (GetMessage(&Message, NULL, 0, 0)) {
        if (!TranslateMDISysAccel(ClientWindow, &Message) &&
            !TranslateAccelerator(GlobalFrameWindow, AccelTable, &Message)) {
             TranslateMessage(&Message);
             DispatchMessage(&Message);
        }
    }

    // WM_QUIT was received.  Cleanup and return.
    WSACleanup();
    ReturnValue = Message.wParam;

 Done:

    return(ReturnValue);

} // WinMain()





BOOL
InitUI(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle)

/*++

Routine Description:

    Initializes chat's user interface.  Registers the window classes
    used by chat and obtains handles to chat's menus.

Arguments:

    InstanceHandle - Supplies the current instance handle.

    PrevInstanceHandle - Supplies the previous instance handle.

Return Value:

    TRUE - Chat successfully initialized

    FALSE - Failure during chat initialization

--*/
{
    WNDCLASS WndClass;           // window class structure
    BOOL     ReturnValue = TRUE; // holds return value

    // If this is the first instance of chat, register the window
    // classes.
    if (!PrevInstanceHandle) {
        WndClass.style         = CS_HREDRAW | CS_VREDRAW;
        WndClass.lpfnWndProc   = MainWndProc;
        WndClass.cbClsExtra    = 0;
        WndClass.cbWndExtra    = 0;
        WndClass.hInstance     = InstanceHandle;
        WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        WndClass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
        WndClass.lpszMenuName  = NULL;
        WndClass.lpszClassName = ChatClassStr;
        if (!RegisterClass(&WndClass)) {
            ReturnValue = FALSE;
            goto Done;
        }

        WndClass.style         = CS_HREDRAW | CS_VREDRAW;
        WndClass.lpfnWndProc   = ConnWndProc;
        WndClass.cbClsExtra    = 0;
        WndClass.cbWndExtra    = CONN_WND_EXTRA;
        WndClass.hInstance     = InstanceHandle;
        WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        WndClass.lpszMenuName  = NULL;
        WndClass.lpszClassName = ConnClassStr;
        if (!RegisterClass(&WndClass)) {
            ReturnValue = FALSE;
            goto Done;
        }
    }

    // Obtain handles to the menus & submenus.
    MainMenu  = LoadMenu(GlobalInstance, "MdiMenuMain");
    if (MainMenu == NULL) {
        ChatSysError("LoadMenu()",
                     "InitUI()",
                     TRUE);
    }
    MainMenuWindow  = GetSubMenu(MainMenu, MAIN_MENU_POS);

 Done:
    
    return(ReturnValue);

} // InitUI()





long CALLBACK
MainWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)

/*++

Routine Description:

    WinSock 2 chat's main window procedure.

Arguments:

    WindowHandle - Supplies the window handle for chat's frame window.

    Message - Supplies the message identifier.

    WParam - Supplies the first message parameter.

    LParam - Supplies the second message parameter.

Return Value:

    Return value depends on the message sent.

--*/
{
    static HWND        ClientWindow; // the MDI client window
    CLIENTCREATESTRUCT ClientCreate; // MDI Client window creation data
    HWND               ChildWindow;  // gets handle to child conn. windows
    MDICREATESTRUCT    MdiCreate;    // MDI Child window creation data
        
    switch (Message) {
        
    case WM_CREATE:

        // Create the client window.
        ClientCreate.hWindowMenu  = MainMenuWindow;
        ClientCreate.idFirstChild = IDM_FIRSTCHILD;
        ClientWindow = CreateWindow("MDICLIENT", 
                                    NULL,
                                    WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
                                    0, 0, 0, 0,
                                    WindowHandle, 
                                    (HMENU)CLIENT_WINDOW_ID,
                                    GlobalInstance, 
                                    (LPSTR)&ClientCreate);
        if (ClientWindow == NULL) {
            ChatSysError("CreateWindow()", 
                         "MainWndProc()",
                         TRUE);
            
        }
            
        return (0);
        
    case WM_COMMAND:

        // We know the wm_command is coming from a menu item, so we
        // can safely assume that the high-order word of WParam is
        // zero. 
        switch (WParam) {

        case IDM_CONNECT:

            // Create a Connection child window.
            MdiCreate.szClass = ConnClassStr;
            MdiCreate.szTitle = "Connecting...";
            MdiCreate.hOwner  = GlobalInstance;
            MdiCreate.x       = CW_USEDEFAULT;
            MdiCreate.y       = CW_USEDEFAULT;
            MdiCreate.cx      = CW_USEDEFAULT;
            MdiCreate.cy      = CW_USEDEFAULT;
            MdiCreate.style   = 0;
            MdiCreate.lParam  = 0;
            ChildWindow = 
              (HWND) SendMessage(ClientWindow, WM_MDICREATE, 0,
                                 (LPARAM)(LPMDICREATESTRUCT)&MdiCreate);

            if (ChildWindow == NULL) {
                ChatSysError("SendMessage(WM_MDICREATE)",
                             "MainWndProc()", 
                             TRUE);
            }

            // Get the connection information from the caller and
            // attempt to make a connection...
            if (!GetConnectionInfo(ChildWindow) || 
                !MakeConnection(ChildWindow)) {
                DestroyWindow(ChildWindow);
            }
            
            break;
            
        case IDM_CLOSE:

            // Close the active window.
            ChildWindow = (HWND)SendMessage(ClientWindow, WM_MDIGETACTIVE, 0, 
                                            0);
            SendMessage(ClientWindow, WM_MDIDESTROY, (WPARAM)ChildWindow, 0);
            break;
            

        case IDM_EXIT:

            // Exit the program.
            SendMessage(WindowHandle, WM_CLOSE, 0, 0);
            break;
            
        case IDM_TILE:

            SendMessage(ClientWindow, WM_MDITILE, 0, 0);
            break;

        case IDM_CASCADE:

            SendMessage(ClientWindow, WM_MDICASCADE, 0, 0);
            break;

        case IDM_ARRANGE:

            SendMessage(ClientWindow, WM_MDIICONARRANGE, 0, 0);
            break;

        case IDM_CLOSEALL:

            // Close all child windows.
            EnumChildWindows(ClientWindow, CloseEnumProc, 0);
            break;

        default:  

            // Pass to active child.
            ChildWindow = (HWND)SendMessage(ClientWindow, WM_MDIGETACTIVE, 
                                            0, 0);
            
            if (IsWindow(ChildWindow)) {
                SendMessage(ChildWindow, WM_COMMAND, WParam, LParam);
            }

            // This causes us to drop out of the switch and to pass
            // the message on to DefFrameProc().
            break; 
        
        } // switch (WParam)
        break;

    case WM_CLOSE:

        // Close all child windows.
        SendMessage(WindowHandle, WM_COMMAND, IDM_CLOSEALL, 0);
        if (GetWindow(ClientWindow, GW_CHILD) != NULL) {
            return (0);
        }
        break;
        
    case USMSG_ACCEPT:

        // Create a MDI Child connection window (even though we aren't
        // yet sure if we will accept the connection -- if we don't,
        // it will be immediately destroyed.)
        MdiCreate.szClass = ConnClassStr;
        MdiCreate.szTitle = "Connection Request...";
        MdiCreate.hOwner  = GlobalInstance;
        MdiCreate.x       = CW_USEDEFAULT;
        MdiCreate.y       = CW_USEDEFAULT;
        MdiCreate.cx      = CW_USEDEFAULT;
        MdiCreate.cy      = CW_USEDEFAULT;
        MdiCreate.style   = 0;
        ChildWindow = (HWND)SendMessage(ClientWindow, WM_MDICREATE, 0,
                                        (LPARAM)(LPMDICREATESTRUCT)&MdiCreate);
        if (ChildWindow == NULL) {
            ChatSysError("SendMessage(WM_MDICREATE)",
                         "MainWndProc()",
                         TRUE);
        }

        // Service the incoming connection request.
        HandleAcceptMessage(ChildWindow, (SOCKET)WParam, LParam);
        break;
        
    case WM_DESTROY:

        // Clean up the listening sockets and kill the application.
        CleanUpSockets();
        PostQuitMessage(0);
        break;

    } // switch(Message)

    // Pass unprocessed messages to DefFrameProc.
    return (DefFrameProc(WindowHandle, 
                         ClientWindow, 
                         Message, 
                         WParam, 
                         LParam));

} // MainWndProc()





long CALLBACK
ConnWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)

/*++

Routine Description:

    Windows procedure for each connection window.

Arguments:

    WindowHandle - Supplies the handle to the connection window which
    is to receive the message.

    Message - Supplies the message identifier.

    WParam - Supplies the first message parameter.

    LParam - Supplies the second message parameter.

Return Value:

    Return value depends on the message sent.

--*/
{
    PCONNDATA         ConnData;    // pointer to data for this connection
    RECT              Rect;        // used for painting the window
    HDC               Hdc;         // device context for painting
                
    switch (Message) {

    case WM_CREATE:

        // Allocate memory for private window data.
        ConnData = (PCONNDATA)malloc(sizeof(CONNDATA));
        if (ConnData == NULL) {
            ChatSysError("malloc()",
                         "ConnWndProc()",
                         TRUE);
        }
        memset((char *)ConnData, 0, sizeof(CONNDATA));

        // Zero is a valid socket ID so set the socket member to INVALID_SOCKET
        ConnData->Socket = INVALID_SOCKET;
        
        GetClientRect(WindowHandle, &Rect);
            
        // Create Send/Recv edit controls.
        ConnData->SendWindow = 
          CreateWindow("EDIT",
                       NULL,
                       WS_CHILD | WS_VISIBLE   | WS_VSCROLL |
                       ES_LEFT  | ES_MULTILINE | ES_AUTOVSCROLL,
                       0,
                       0,
                       Rect.right,
                       Rect.bottom/2,
                       WindowHandle,
                       (HMENU)EC_SEND_CHILD,
                       GlobalInstance,
                       NULL);
        
        ConnData->RecvWindow = 
          CreateWindow("EDIT",
                       NULL,
                       WS_CHILD | WS_VISIBLE   | WS_VSCROLL |
                       ES_LEFT  | ES_MULTILINE | ES_AUTOVSCROLL,
                       0,
                       Rect.bottom/2 + 1,
                       Rect.right,
                       Rect.bottom - Rect.bottom/2 - 1,
                       WindowHandle,
                       (HMENU)EC_RECV_CHILD,
                       GlobalInstance,
                       NULL);

        if (!ConnData->SendWindow || !ConnData->RecvWindow) {
            ChatSysError("CreateWindow()",
                         "ConnWndProc()",
                         TRUE);
        }

        // Fill in some connection-specific data.
        ConnData->SocketEventObject = CreateEvent(NULL, 
                                                  FALSE, 
                                                  FALSE, 
                                                  NULL); 
        ConnData->OutputEventObject = CreateEvent(NULL, 
                                                  FALSE, 
                                                  FALSE, 
                                                  NULL);
        ConnData->OutputQueue = QCreate();
        if (!ConnData->SocketEventObject || !ConnData->OutputEventObject ||
            !ConnData->OutputQueue) {
            ChatSysError("CreateEvent() or QCreate()",
                         "ConnWndProc()",
                         TRUE);
        }
        ConnData->ConnectionWindow = WindowHandle;
        ConnData->WriteOk = FALSE;
                
        // Subclass the edit controls. Both functions necessarily
        // return the same FARPROC...
        OldEditWndProc = (FARPROC)SetWindowLong(ConnData->SendWindow, 
                                                GWL_WNDPROC, 
                                                (DWORD)SendEditSubClass);
        SetWindowLong(ConnData->RecvWindow, 
                      GWL_WNDPROC,
                      (DWORD)RecvEditSubClass);

        // Split the connection window in half.
        Hdc = GetDC(WindowHandle);
        MoveToEx(Hdc, 0, Rect.bottom/2, NULL);
        LineTo(Hdc, Rect.right, Rect.bottom/2);
        ReleaseDC(WindowHandle, Hdc);
                                                  
        // Set the private window data to be a pointer to ConnData.
        SetWindowLong(WindowHandle, GWL_CONNINFO, (LONG)ConnData);
        
        return (0);
        
    case WM_COMMAND:

        switch (HIWORD(WParam)) {

        case 0:

            // If the high-order word is zero, this message is being
            // sent from a menu-item (hopefully it was passed to us
            // from the frame window, since the mdi child connection
            // windows, of course, have no menus).
            switch (LOWORD(WParam)) {

            case IDM_CLEAR_SENDBUFFER:

                // Clear the send buffer.
                ConnData = GetConnData(WindowHandle);
                PostMessage(ConnData->SendWindow, EM_SETSEL, 0, -1);
                PostMessage(ConnData->SendWindow, WM_CLEAR, 0, 0);
                return (0);
                                
            case IDM_CLEAR_RECVBUFFER:
                
                // Clear the receive buffer.
                ConnData = GetConnData(WindowHandle);
                PostMessage(ConnData->RecvWindow, EM_SETSEL, 0, -1);
                PostMessage(ConnData->RecvWindow, WM_CLEAR, 0, 0);
                return (0);
                
            default:

                // An unknown menu item... just return.
                return (0);

            } // switch (LOWORD(WParam))
            
        case 1:
            
            // The high-order word is 1, so thus the wm_command
            // message is coming from an accelerator. Ignore it.
            return(0);
            
        default:
            
            // Let the default window procedure handle it (below).
            break;
            
        } // switch (HIWORD(WParam))
        break;
            

    case WM_PAINT:

        // Window needs to be painted, split the connection window in
        // half.
        GetClientRect(WindowHandle, &Rect);
        Hdc = GetDC(WindowHandle);
        MoveToEx(Hdc, 0, Rect.bottom/2, NULL);
        LineTo(Hdc, Rect.right, Rect.bottom/2);
        ReleaseDC(WindowHandle, Hdc);

        return (DefMDIChildProc(WindowHandle, 
                                Message, 
                                WParam, 
                                LParam));
                
    case WM_SIZE:

        ConnData = GetConnData(WindowHandle);

        // Resize the edit control windows.
        MoveWindow(ConnData->SendWindow,
                   0,
                   0,
                   LOWORD(LParam),
                   HIWORD(LParam)/2,
                   TRUE);
            
        MoveWindow(ConnData->RecvWindow,
                   0,
                   HIWORD(LParam)/2 + 1,
                   LOWORD(LParam),
                   HIWORD(LParam) - HIWORD(LParam)/2 - 1,
                   TRUE);

        return (DefMDIChildProc(WindowHandle,
                                Message, 
                                WParam, 
                                LParam));
        
    case USMSG_CONNECT:

        // Handle the new connection.
        HandleConnectMessage(WindowHandle, LParam);
        return(0);
        
    case WM_DESTROY:

        ConnData = GetConnData(WindowHandle);
        CleanupConnection(ConnData);
        
        return(DefMDIChildProc(WindowHandle, 
                               Message, 
                               WParam, 
                               LParam));

    } // switch (Message)

    // Pass unprocessed message to DefMDIChildProc.
    return(DefMDIChildProc(WindowHandle, 
                           Message, 
                           WParam, 
                           LParam));

} // ConnWndProc()





long CALLBACK
SendEditSubClass(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)

/*++

Routine Description:

    Window procedure used to subclass the edit control for the sending
    window.  

Implementation:

    Text is only allowed to be appended to or deleted from the end of
    an edit control's text buffer.  When text is either typed or
    paste, Chat creates an output request data structure and sticks it
    on the output thread for this connection.

Arguments:

    WindowHandle - Supplies the handle to the edit control's window

    Message - Supplies the message identifier.

    WParam - Supplies the first message parameter.

    LParam - Supplies the second message parameter.

Return Value:

    ReturnValue - Depends of the message sent.

--*/
{
    int             Index;        // an index into the edit control's text
    HANDLE          CBDataHandle; // handle to the clipboard data
    LPSTR           CBDataPtr;    // pointer to data to send
    PCONNDATA       ConnData;     // pointer to connection-specific data
    POUTPUT_REQUEST OutReq;       // output request data
    int             TextSize;     // number of bytes of text to send
    DWORD           BytesLeft;    // number of bytes left to send

    ConnData = GetConnData(GetParent(WindowHandle));
        
    switch (Message) {

    case WM_CHAR:

        // If the character is sendable, enqueue the character on the
        // output queue, and signal the output event.
        if (IsSendable((char)WParam)) {
            
            OutReq = (POUTPUT_REQUEST) malloc(sizeof(OUTPUT_REQUEST));
            if (OutReq == NULL) {
                ChatSysError("malloc()",
                             "SendEditSubClass()",
                             TRUE);
            }

            // Rather than malloc a one-character buffer, just point
            // Buffer into the already-allocated Char field of the
            // OutReq structure.  This gets rid of a heavy-weight
            // malloc for each character typed.
            OutReq->Buffer.len = sizeof(char);
            OutReq->Buffer.buf = &OutReq->Character;
                        
            // Fill in the OutReq structure.
            *(OutReq->Buffer.buf) = (char)WParam;
            OutReq->Type = NON_OVERLAPPED_IO;
            OutReq->ConnData = ConnData;
            
            QInsert(ConnData->OutputQueue, (LPVOID)OutReq);
            SetEvent(ConnData->OutputEventObject);   
        }

        // Move the cursor to the end of the text buffer.
        Index = GetWindowTextLength(WindowHandle);
        CallWindowProc((WNDPROC)OldEditWndProc,
                       WindowHandle, 
                       EM_SETSEL, 
                       Index, 
                       Index);

        // Make room for the new text in the edit control.
        MakeRoom(WindowHandle, 1);
        
        // Pass on the message to the original Edit Window Procedure.
        return (CallWindowProc((WNDPROC)OldEditWndProc,
                               WindowHandle, 
                               Message, 
                               WParam,
                               LParam));
        
    case WM_PASTE:

        // Get the buffer from the edit control and send it over the
        // socket.
        if (IsClipboardFormatAvailable(CF_TEXT)) {
            
            if (OpenClipboard(WindowHandle)) {
                CBDataHandle = GetClipboardData(CF_TEXT);
                
                if (CBDataHandle) {
                    
                    CBDataPtr = GlobalLock(CBDataHandle);
                    TextSize = strlen(CBDataPtr);
                    
                    // Make room in the edit control for the new text; if
                    // this is impossible, just return because MakeRoom
                    // notifies the user internally.
                    if (!MakeRoom(WindowHandle, TextSize)) {
                        GlobalUnlock(CBDataHandle);
                        CloseClipboard();
                        return(0);
                    }
                    
                    // If the text is longer than the maximum message
                    // size, we need to split it into bite-size pieces
                    // for consumption by the transport layer.
                    BytesLeft = TextSize;
                    while (BytesLeft != 0) {

                        // Create a new output request.
                        OutReq = 
                          (POUTPUT_REQUEST)malloc(sizeof(OUTPUT_REQUEST));
                        if (OutReq == NULL) {
                            ChatSysError("malloc()", 
                                         "SendEditSubClass",
                                         TRUE);
                        }

                        // Determine how much data to send with this
                        // request, and allocate a buffer that size.
                        if (BytesLeft > ConnData->MaxMsgSize) {
                            OutReq->Buffer.len = ConnData->MaxMsgSize;
                        } else {
                            OutReq->Buffer.len = BytesLeft;
                        }
                        OutReq->Buffer.buf = 
                          (char *)malloc(OutReq->Buffer.len);
                        if (OutReq->Buffer.buf == NULL) {
                            ChatSysError("malloc()", 
                                         "SendEditSubClass",
                                         TRUE);
                        }

                        // Fill in the rest of the OutReq structure
                        // and put it in the output queue.
                        OutReq->Type = OVERLAPPED_IO;
                        OutReq->ConnData = ConnData;
                        memcpy(OutReq->Buffer.buf, CBDataPtr, 
                               OutReq->Buffer.len);
                        QInsert(ConnData->OutputQueue, (LPVOID)OutReq);
                        
                        // Update the number of bytes left to send and
                        // the pointer to the rest of the data.
                        BytesLeft -= OutReq->Buffer.len;
                        CBDataPtr += OutReq->Buffer.len;
                    }
                    
                    // Signal the ouput event.
                    SetEvent(ConnData->OutputEventObject);
                    GlobalUnlock(CBDataHandle);

                } else {

                    MessageBox(WindowHandle,
                               "Sorry, couldn't retrieve the clipoard data.",
                               "Error.", 
                               MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                }

                CloseClipboard();

            } // if (OpenClipboard(WindowHandle))
            else {
                
                MessageBox(WindowHandle,
                           "Couldn't open the clipboard!!",
                           "Try again.", 
                           MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            }

        } // if (IsClipboardFormatAvailable(CF_TEXT)) 
        else {
            
            MessageBox(WindowHandle,
                       "Sorry, the CF_TEXT format not available.",
                       "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        }
        // Move the cursor to the end of the text buffer.
        Index = GetWindowTextLength(WindowHandle);
        CallWindowProc((WNDPROC)OldEditWndProc,
                       WindowHandle, 
                       EM_SETSEL, 
                       Index, 
                       Index);

        // Call the real edit control window procedure.
        return (CallWindowProc((WNDPROC)OldEditWndProc,
                               WindowHandle, 
                               Message, 
                               WParam, 
                               LParam));
        
    case WM_KEYDOWN:

        // Capture the delete key. nVirtKey: 46 = 'Del'.
        if (WParam == 46) {
            return(0);
        } else {
            return (CallWindowProc((WNDPROC)OldEditWndProc, WindowHandle,
                                   Message, WParam, LParam));
        }
        
    case WM_CUT:  // Flow through
    case WM_UNDO:

        return (0);
        
    default:

        return (CallWindowProc((WNDPROC)OldEditWndProc,
                               WindowHandle, 
                               Message, 
                               WParam, 
                               LParam));
    
    } // switch (Message)

} // SendEditSubClass()





long CALLBACK
RecvEditSubClass(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)

/*++

Routine Description:

    Window procedure used to subclass the edit control for the
    receiving window.  

Implementation:

    The user cannot modify any of the text displayed in the receiving
    edit control. All text received from the socket associated with
    this edit control is displayed. 

Arguments:

    WindowHandle - Supplies the handle to the edit control's window

    Message - Supplies the message identifier.

    WParam - Supplies the first message parameter.

    LParam - Supplies the second message parameter.

Return Value:

    ReturnValue - Depends of the message sent.

--*/
{
    switch (Message) {

    case WM_CHAR:       // Flow through
    case WM_KEYDOWN:    // Flow through
    case WM_UNDO:       // Flow through
    case WM_PASTE:      // Flow through
    case WM_CUT:

        return (0);     // Effectively disables the above messages.
        
    default:

        return (CallWindowProc((WNDPROC)OldEditWndProc,
                               WindowHandle, 
                               Message, 
                               WParam, 
                               LParam));
    }

} // RecvEditSubClass()





BOOL CALLBACK
CloseEnumProc(
    IN HWND WindowHandle,
    IN LONG LParam)

/*++
                                                                          
Routine Description:

    A callback function used to destroy all of chat's connection
    windows. 

Arguments:

    WindowHandle - Supplies the handle to the child's window which
                   is to be closed.

    LParam - Not Used.

Return Value:

    TRUE - Returns true in order to continue enumeration.

--*/
{
    if (!GetWindow(WindowHandle, GW_OWNER)) {
        
        SendMessage(GetParent(WindowHandle), WM_MDIRESTORE, 
                    (WPARAM)WindowHandle, 0);
        
        if (SendMessage(WindowHandle, WM_QUERYENDSESSION, 0, 0)) {
            SendMessage(GetParent(WindowHandle), WM_MDIDESTROY, 
                        (WPARAM)WindowHandle, 0);
        }
    }
    return (TRUE);
    
} // CloseEnumProc()





void
OutputString(
    IN HWND RecvWindow,
    IN char *String)
/*++
                                                                   
Routine Description:

    This function sends the string received over the socket to the
    edit window given by RecvWindow.  

Implementation:

    This function treats certain non-printable characters specially so
    that they mean the same thing on this end as they did on the end
    that sent them. 

Arguments:

    RecvWindow -- Handle to the edit control window which is to
    receive the text.

    String -- Points to the NULL-terminated string to be output.  

Return Value:

    None.

--*/
{

    int  Index, Index1;         // indices into the edit control text
    char *Current = String;     // the character we are examining
    char *First = String;       // the first character we have not output
    char Buffer[BUFFER_LENGTH]; // scratch buffer for SendMessage
    int  NumChars;              // how many characters we put in Buffer

    // Make room for the string in the edit control.
    MakeRoom(RecvWindow, strlen(String));

    // This loop goes through the whole string we have been instructed
    // to print in the edit control; when it encounters certain
    // characters in the string, it sends certain messages to the edit
    // control to simulate the right behavior.
    while (*Current) {
     
        switch (*Current) {

        case '\b':
            
            // The current character is a backspace.  Print out the
            // string we have so far, and then manually erase a
            // character. 
            *Current = '\0';

            // Output the string at 'First'...
            Index = GetWindowTextLength(RecvWindow);
            SendMessage(RecvWindow, EM_SETSEL, Index, Index);
            SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)First);

            // Get the last line of the text into 'Buffer'.
            Index = GetWindowTextLength(RecvWindow);
            Index1 = SendMessage(RecvWindow, EM_LINEFROMCHAR, Index, 0);
            *((LPDWORD)Buffer) = BUFFER_LENGTH;
            NumChars = SendMessage(RecvWindow, EM_GETLINE, Index1, 
                                   (LPARAM)Buffer);

            // If the line had no characters in it, then it's the
            // beginning of a new line, and we should erase two
            // characters (the preceding \r\n) rather than just one.
            if (NumChars == 0) {
                SendMessage(RecvWindow, EM_SETSEL, Index - 2, Index);
                SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)"");
            } else {
                SendMessage(RecvWindow, EM_SETSEL, Index - 1, Index);
                SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)"");
            }

            First = Current + 1;
            break;

        case '\r':

            // A newline.  If the Chat window is minimized, ask the
            // user if they want it maximized.
            QueryAndMaximize();

            // If a remote user typed a newline, we'll receive '\r',
            // but if the remote user pasted it in, we'll get '\r\n'. 
            if (*(Current + 1) != '\n') {
                
                // The newline was typed in...output the string up to
                // the newline, then "\r\n".
                *Current = '\0';
                
                Index = GetWindowTextLength(RecvWindow);
                SendMessage(RecvWindow, EM_SETSEL, Index, Index);
                SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)First);
                
                Index = GetWindowTextLength(RecvWindow);
                SendMessage(RecvWindow, EM_SETSEL, Index, Index);
                SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)"\r\n");
                
                First = Current + 1;
            }
            break;

        default:
            
            break;
        
        } // switch (*Current)
        Current++;
    
    } // while (*Current)

    // Ouptut any string leftover in First
    Index = GetWindowTextLength(RecvWindow);
    SendMessage(RecvWindow, EM_SETSEL, Index, Index);
    SendMessage(RecvWindow, EM_REPLACESEL, 0, (LPARAM)First);
}





void
QueryAndMaximize(void) 
/*++
                                                               
Routine Description:

    If Chat's main frame window is minimized, this function asks the
    user if they want to activate and display Chat, and acts
    accordingly. 

Arguments:

    None.

Return Value:

    None.

--*/
{
    WINDOWPLACEMENT WinPlace; // holds window placement data
    
    WinPlace.length = sizeof(WINDOWPLACEMENT);

    // Get chat's main window's state.
    GetWindowPlacement(GlobalFrameWindow, &WinPlace);

    // If it's minimized, ask the user if they want to maximize.
    if (WinPlace.showCmd == SW_SHOWMINIMIZED) {

        if (MessageBox(GlobalFrameWindow, 
                       "There is activity on a Chat connection.  Maximize?",
                       "Chat has data.",
                       MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND) 
            == IDYES) {

            // Maximize chat's main window.
            WinPlace.showCmd = SW_RESTORE;
            SetWindowPlacement(GlobalFrameWindow, &WinPlace);
        }
    }
} // QueryAndMaximize()





BOOL
GetConnectionInfo(
    IN HWND ConnectionWindow)

/*++

Routine Description:

    Queries the user as to what sort of connection (i.e. what address
    family/protocol) she wants to make.  

Implementation:

    After popping up a listbox, filling it in with all the available
    protocol choices, and getting the user's choice, this function
    uses it to determine what kind of dialog box to pop up to get the
    actual connection parameters, which are stored in the CONNDATA
    associated with the given ConnectionWindow.

Arguments:

    ConnectionWindow -- Handle to the connection window associated
    with this connection.

Return Value:

    TRUE - The user closed a dialog box by clicking the 'Ok' button.

    FALSE - The user closed a dialog box by clicking the 'Cancel'
            button.

--*/

{
    PCONNDATA ConnData;           // connection-specific data
    BOOL      ReturnValue = TRUE; // holds the return value

    ConnData = GetConnData(ConnectionWindow);
        
    // Pop up a dialog box from which the user chooses the protocol; a
    // pointer to a protocol info struct is stored in ConnData.
    if (!DialogBoxParam(GlobalInstance, 
                        "ChooseFamilyDlg", 
                        ConnectionWindow, 
                        ChooseFamilyDlgProc, 
                        (LPARAM)ConnData)) {
        ReturnValue = FALSE;
        goto Done;
    }
    
    // The user has selected a protocol; now pop up an address family
    // specific dialog box to get the address to which we want to make
    // a chat connection, a pointer to which is stored in
    // ConnData->SockAddr (see InetConnDlg, for example).
    switch (ConnData->ProtocolInfo->iAddressFamily) {

    case AF_INET:
        
        ReturnValue = DialogBoxParam(GlobalInstance, 
                                     "InetConnDlg", 
                                     ConnectionWindow,
                                     InetConnDlgProc, 
                                     (LPARAM)ConnData);
        goto Done;

    default:
        
        ReturnValue = DialogBoxParam(GlobalInstance, 
                               "DefaultConnDlg", 
                               ConnectionWindow, 
                               DefaultConnDlgProc,
                               (LPARAM)ConnData);
        goto Done;
    }

 Done:

    return(ReturnValue);

} // GetConnectionInfo()





BOOL
TranslateHex(
    OUT LPVOID Buffer,
    IN  int    BufferLen,
    IN  char   *HexString,
    IN  HWND   WindowHandle)
/*++

Routine Description:

    This function converts an arbitrarily long string of hexidecimal
    digits into a binary representation.  

    Example -- if the string "0fbf" is passed in, then two bytes will
    be written into Buffer -- 15 and 191.

Arguments:

    Buffer -- Points to a buffer where we want the data stored.

    BufferLen -- The length, in bytes, of Buffer.

    HexString -- A null-terminated string of digits with values in the
    ASCII range of 0-9 and a-f.
 
    WindowHandle -- Window handle needed in case we pop up a dialog
    box. 

Return Value:

    TRUE -- HexString was successfully translated.

    FALSE -- One of the characters does not represent a hex digit, or
    the HexString is too long to be translated into BufferLen bytes.

--*/
{

    int  HexStringLen;       // the length of the HexString, in bytes
    int  i;                  // counting variable
    char TwoCharString[3];   // stores two hex characters + a NULL char
    char *NextByte;          // next free byte in the buffer
    BOOL ReturnValue = TRUE; // return value

    HexStringLen  = strlen(HexString);
    NextByte = (char *)Buffer;
    TwoCharString[2] = '\0';

    // Zero out the output buffer.
    memset(NextByte, 0, BufferLen);
    
    // If the hex string in more than twice as long as the buffer, we
    // don't have a big enough buffer.
    if ((HexStringLen / 2) > BufferLen) {
        ReturnValue = FALSE;
    } else {

        // Go through the hex string two characters at a time...
        for (i = 0; i < HexStringLen; i += 2) {
            
            // Copy the next two bytes of the hex string into
            // TwoCharString; then check to make sure they are both
            // hexidecimal digits.
            memcpy(TwoCharString, HexString + i, 2);
            if (!isxdigit(TwoCharString[0]) || !isxdigit(TwoCharString[1])) {
                MessageBox(WindowHandle, "Type a hexidecimal number, please",
                           "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                ReturnValue = FALSE;
                break;
            }
            *NextByte++ = (char)strtol(TwoCharString, NULL, 16);

        } // for
    } // else
    return(ReturnValue);

} // TranslateHex()





BOOL
PackTwoStrings(
    OUT char  *Buffer,
    IN  int   BufferLen,
    IN  char  *String1,
    IN  char  *String2)
/*++

Routine Description:

    Packs two strings into one buffer.  Useful for packing caller data
    into a buffer for connection-time data transfer.  It is not an
    error if either string is of zero length, i.e. just the '\0'
    character. 

Arguments:

    Buffer -- Pointer to a buffer into which the two strings will be
    packed. 

    BufferLen -- The length, in bytes, of Buffer.

    String1 - Address of the first NULL terminated string.
     
    String2 - Address of the second NULL terminated string.

Return Value:

    TRUE -- The strings were successfully packed into the buffer.

    FALSE -- The passed in buffer was NULL, one or both of the strings
    were NULL, or Buffer is too small.
    
--*/

{
    int Length1, Length2;    // holds the length of the strings
    BOOL ReturnValue = TRUE; // holds the return value

    if (!Buffer || !String1 || !String2) {
        ReturnValue = FALSE;
        goto Done;
    }
        
    Length1 = strlen(String1);
    Length2 = strlen(String2);
    
    if (BufferLen < (Length1 + Length2 + 2)) {
        ReturnValue = FALSE;
        goto Done;
    }
    
    strcpy(Buffer, String1);
    strcpy(Buffer + Length1 + 1, String2);
 
 Done:
   
    return(ReturnValue);

} // PackTwoStrings()





BOOL
ExtractTwoStrings(
    IN  char *Buffer,
    OUT char *String1,
    IN  char Length1,
    OUT char *String2,
    IN  int  Length2)

/*++

Routine Description:

    Extracts two strings from a buffer, expecting them to be packed as
    PackTwoStrings does it.

Arguments:

    Buffer -- The buffer which contains two strings.

    String1 -- A pointer to a buffer of size Length1.

    Length1 -- The length, in bytes, of String1.

    String2 -- A pointer to a buffer of size Length2.

    Length2 -- The length, in bytes, of String2.

Return Value:

    TRUE -- The strings were successfully extracted.

    FALSE -- One of the pointers was NULL, or a packed string was too
    big to be extracted out into a buffer.

--*/

{
    int BufStrLen1, BufStrLen2; // the length of the packed strings
    char *BufStr2;              // points to the second packed string
    BOOL ReturnValue = TRUE;    // holds the return value

    if (!Buffer || !String1 || !String2) {
        ReturnValue = FALSE;
        goto Done;
    }
    
    BufStrLen1 = strlen(Buffer);
    BufStr2 = Buffer + BufStrLen1 + 1;
    BufStrLen2 = strlen(BufStr2);
    
    if ((BufStrLen1 > Length1) || (BufStrLen2 > Length2)) {
        ReturnValue = FALSE;
        goto Done;
    }

    strcpy(String1, Buffer);
    strcpy(String2, BufStr2);

 Done:
 
   return(ReturnValue);

} // ExtractTwoStrings()





void 
ChatSysError(
    IN char *FailedFunction,
    IN char *InFunction,
    IN BOOL Fatal)
/*++

Routine Description:

    Pops up a standard message box to display fatal system errors; if
    the error is fatal, the process is ended and this function does
    not return.    

Arguments:

    FailedFunction -- Pointer to a string that contains the name of
    the system function that failed.

    InFunction -- Pointer to a string that contains the name of the
    chat function in which the failure occurred.

    Fatal -- Boolean indicating whether the error is fatal to the
    process or not.

Return Value:

    None.

--*/
{

    char MsgText[MSG_LEN]; // holds message strings

    wsprintf(MsgText,
             "System call %s failed in chat function %s. Error code: %d",
             FailedFunction, InFunction, GetLastError());
    MessageBox(GlobalFrameWindow, 
               MsgText, 
               Fatal ? "Fatal Error." : "Non-Fatal Error.",
               MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);

    if (Fatal) {
        ExitProcess(CHAT_ERROR);
    }

} // ChatSysError()





BOOL
MakeRoom(
    IN HWND EditControl,
    IN int  HowMuch)
/*++

Routine Description:

    This function makes room in an edit control for more text, if
    necessary. 
 
Arguments:

    EditControl -- The edit control in question.

    HowMuch -- How many characters do we want to put into the edit
    control? 

Return Value:

    TRUE -- If there was not enough room in the edit control, MakeRoom
    successfully chopped off some text from the beginning, and there
    is now enough room.

    FALSE -- The requested amount can not fit in the edit control,
    period; a message box has informed the user.

--*/
{

    int  TextLength;  // how much text is in the edit control
    int  CharIndex1;
    int  CharIndex2;
    int  LineIndex;   // index variables
    BOOL ReturnValue; // return value

    if (HowMuch > MAX_EC_TEXT) {
        
        MessageBox(EditControl, 
                   "Clipboard too long. Try pasting a smaller amount.",
                   "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;

    } else {

        TextLength = GetWindowTextLength(EditControl);
        
        if (TextLength + HowMuch > MAX_EC_TEXT) {
            
            // Inserting the text would exceed our limit.  This code
            // chops off roughly the first half of the edit control's
            // text. 
            CharIndex1 = TextLength / 2;
            LineIndex = SendMessage(EditControl, EM_LINEFROMCHAR,
                                    (WPARAM)CharIndex1, 0);
            CharIndex2 = SendMessage(EditControl, EM_LINEINDEX,
                                     (WPARAM)LineIndex, 0);
            
            SendMessage(EditControl, EM_SETSEL, 0, CharIndex2);
            SendMessage(EditControl, EM_REPLACESEL, 0, 
                        (LPARAM)"");
        }

        ReturnValue = TRUE;        
    } // else

    return(ReturnValue);
} // MakeRoom()
