/***************************************************************************
 *                                                                         *
 *  PROGRAM	: ddemlcl.c						    *
 *                                                                         *
 *  PURPOSE     : To demonstrate how to use the DDEML library from the     *
 *                client side and for basic testing of the DDEML API.      *
 *                                                                         *
 ***************************************************************************/

#include "ddemlcl.h"
#include <string.h>
#include <memory.h>
#include "infoctrl.h"

/* global variables used in this module or among more than one module */
CONVCONTEXT CCFilter = { sizeof(CONVCONTEXT), 0, 0, 0, 0L, 0L };
DWORD idInst = 0;
HANDLE hInst;                       /* Program instance handle               */
HANDLE hAccel;                      /* Main accelerator resource             */
HWND hwndFrame           = NULL;    /* Handle to main window                 */
HWND hwndMDIClient       = NULL;    /* Handle to MDI client                  */
HWND hwndActive          = NULL;    /* Handle to currently activated child   */
LONG DefTimeout      = DEFTIMEOUT;  /* default synchronous transaction timeout */
WORD wDelay = 0;
BOOL fBlockNextCB = FALSE;     /* set if next callback causes a CBR_BLOCK    */
BOOL fTermNextCB = FALSE;      /* set to call DdeDisconnect() on next callback */
BOOL fAutoReconnect = FALSE;   /* set if DdeReconnect() is to be called on XTYP_DISCONNECT callbacks */
WORD fmtLink = 0;                   /* link clipboard format number          */
WORD DefOptions = 0;                /* default transaction optons            */
OWNED aOwned[MAX_OWNED];            /* list of all owned handles.            */
WORD cOwned = 0;                    /* number of existing owned handles.     */
FILTERPROC *lpMsgFilterProc;	    /* instance proc from MSGF_DDEMGR filter */


 /*
 * This is the array of formats we support
 */
FORMATINFO aFormats[] = {
    { CF_TEXT, "CF_TEXT" },       // exception!  predefined format
    { 0, "Dummy1"  },
    { 0, "Dummy2"  },
};

/* Forward declarations of helper functions in this module */
VOID NEAR PASCAL CloseAllChildren(VOID);
VOID NEAR PASCAL InitializeMenu (HANDLE);
VOID NEAR PASCAL CommandHandler (HWND,WORD);
VOID NEAR PASCAL SetWrap (HWND,BOOL);

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WinMain(HANDLE, HANDLE, LPSTR, int)                        *
 *                                                                          *
 *  PURPOSE    : Creates the "frame" window, does some initialization and   *
 *               enters the message loop.                                   *
 *                                                                          *
 ****************************************************************************/
int PASCAL WinMain(hInstance, hPrevInstance, lpszCmdLine, nCmdShow)

HANDLE hInstance;
HANDLE hPrevInstance;
LPCSTR  lpszCmdLine;
int    nCmdShow;
{
    MSG msg;

    hInst = hInstance;

    /* If this is the first instance of the app. register window classes */
    if (!hPrevInstance){
        if (!InitializeApplication ())
            return 0;
    }

    /* Create the frame and do other initialization */
    if (!InitializeInstance(nCmdShow))
        return 0;

    /* Enter main message loop */
    while (GetMessage (&msg, NULL, 0, 0)){
	(*lpMsgFilterProc)(MSGF_DDEMGR, 0, (LONG)(LPMSG)&msg);
    }

    // free up any appowned handles
    while (cOwned) {
        DdeFreeDataHandle(aOwned[--cOwned].hData);
    }
    DdeUninitialize(idInst);

    UnhookWindowsHook(WH_MSGFILTER, (FARPROC)lpMsgFilterProc);
    FreeProcInstance((FARPROC)lpMsgFilterProc);

    return 0;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : FrameWndProc (hwnd, msg, wParam, lParam )                  *
 *                                                                          *
 *  PURPOSE    : The window function for the "frame" window, which controls *
 *               the menu and encompasses all the MDI child windows. Does   *
 *               the major part of the message processing. Specifically, in *
 *               response to:                                               *
 *                                                                          *
 ****************************************************************************/
LONG FAR PASCAL __export FrameWndProc ( hwnd, msg, wParam, lParam )

register HWND    hwnd;
UINT		 msg;
register WPARAM    wParam;
LPARAM		   lParam;

{
    switch (msg){
        case WM_CREATE:{
            CLIENTCREATESTRUCT ccs;

            /* Find window menu where children will be listed */
            ccs.hWindowMenu = GetSubMenu (GetMenu(hwnd),WINDOWMENU);
            ccs.idFirstChild = IDM_WINDOWCHILD;

            /* Create the MDI client filling the client area */
            hwndMDIClient = CreateWindow ("mdiclient",
                                          NULL,
                                          WS_CHILD | WS_CLIPCHILDREN |
                                          WS_VSCROLL | WS_HSCROLL,
                                          0,
                                          0,
                                          0,
                                          0,
                                          hwnd,
                                          0xCAC,
                                          hInst,
                                          (LPSTR)&ccs);


            ShowWindow (hwndMDIClient,SW_SHOW);
            break;
        }

        case WM_INITMENU:
            InitializeMenu ((HMENU)wParam);
            break;

        case WM_COMMAND:
            CommandHandler (hwnd,wParam);
            break;

        case WM_CLOSE:
            CloseAllChildren();
            DestroyWindow(hwnd);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            /*  use DefFrameProc() instead of DefWindowProc() since there
             *  are things that have to be handled differently because of MDI
             */
            return DefFrameProc (hwnd,hwndMDIClient,msg,wParam,lParam);
    }
    return 0;
}





/****************************************************************************
 *                                                                          *
 *  FUNCTION   : MDIChildWndProc                                            *
 *                                                                          *
 *  PURPOSE    : The window function for the "child" conversation and list  *
 *               windows.                                                   *
 *                                                                          *
 ****************************************************************************/
LONG FAR PASCAL __export MDIChildWndProc( hwnd, msg, wParam, lParam )
register HWND   hwnd;
UINT		msg;
register WPARAM   wParam;
LPARAM		  lParam;
{
    MYCONVINFO *pmci;
    RECT rc;

    switch (msg){
    case WM_CREATE:
        /*
         * Create a coresponding conversation info structure to link this
         * window to the conversation or conversation list it represents.
         *
         * lParam: points to the conversation info to initialize our copy to.
         */
        pmci = (MYCONVINFO *)MyAlloc(sizeof(MYCONVINFO));
        if (pmci != NULL) {
            _fmemcpy(pmci,
                    (LPSTR)((LPMDICREATESTRUCT)((LPCREATESTRUCT)lParam)->lpCreateParams)->lParam,
                    sizeof(MYCONVINFO));
            pmci->hwndXaction = 0;              /* no current transaction yet */
            pmci->x = pmci->y = 0;              /* new transaction windows start here */
            DdeKeepStringHandle(idInst, pmci->hszTopic);/* keep copies of the hszs for us */
            DdeKeepStringHandle(idInst, pmci->hszApp);

             // link hConv and hwnd together
            SetWindowWord(hwnd, 0, (WORD)pmci);

            /*
             * non-list windows link the conversations to the windows via the
             * conversation user handle.
             */
            if (!pmci->fList)
		DdeSetUserHandle(pmci->hConv, (DWORD)QID_SYNC, (DWORD)hwnd);
        }
        goto CallDCP;
        break;

    case UM_GETNEXTCHILDX:
    case UM_GETNEXTCHILDY:
        /*
         * Calculate the next place to put the next transaction window.
         */
        {
            pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
            GetClientRect(hwnd, &rc);
            if (msg == UM_GETNEXTCHILDX) {
                pmci->x += 14;
                if (pmci->x > (rc.right - 200 - rc.left))
                    pmci->x = 0;
                return(pmci->x);
            } else {
                pmci->y += 12;
                if (pmci->y > (rc.bottom - 100 - rc.top))
                    pmci->y = 0;
                return(pmci->y);
            }
        }
        break;

    case UM_DISCONNECTED:
        /*
         * Disconnected conversations can't have any transactions so we
         * remove all the transaction windows here to show whats up.
         */
        {
            HWND hwndT;
            while (hwndT = GetWindow(hwnd, GW_CHILD))
                DestroyWindow(hwndT);
            InvalidateRect(hwnd, NULL, TRUE);
        }
        break;

    case WM_DESTROY:
        /*
         * Cleanup our conversation info structure, and disconnect all
         * conversations associated with this window.
         */
        pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
        pmci->hwndXaction = 0;      /* clear this to avoid focus problems */
        if (pmci->hConv) {
            if (pmci->fList) {
                DdeDisconnectList((HCONVLIST)pmci->hConv);
            } else {
                MyDisconnect(pmci->hConv);
            }
        }
        DdeFreeStringHandle(idInst, pmci->hszTopic);
        DdeFreeStringHandle(idInst, pmci->hszApp);
        MyFree(pmci);
        goto CallDCP;
        break;

    case WM_SETFOCUS:
        /*
         * This catches focus changes caused by dialogs.
         */
        wParam = TRUE;
        // fall through

    case WM_MDIACTIVATE:
        hwndActive = wParam ? hwnd : NULL;
        pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
        /*
         * pass the focus onto the current transaction window.
         */
        if (wParam && IsWindow(pmci->hwndXaction))
            SetFocus(pmci->hwndXaction);
        break;

    case ICN_HASFOCUS:
        /*
         * update which transaction window is the main one.
         */
        pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
        pmci->hwndXaction = wParam ? HIWORD(lParam) : NULL;
        break;

    case ICN_BYEBYE:
        /*
         * Transaction window is closing...
         *
         * wParam = hwndXact
         * lParam = lpxact
         */
        pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
        {
            XACT *pxact;

            pxact = (XACT *)LOWORD(lParam);
            /*
             * If this transaction is active, abandon it first.
             */
            if (pxact->fsOptions & XOPT_ASYNC &&
                    !(pxact->fsOptions & XOPT_COMPLETED)) {
                DdeAbandonTransaction(idInst, pmci->hConv, pxact->Result);
            }
            /*
             * release resources associated with transaction.
             */
            DdeFreeStringHandle(idInst, pxact->hszItem);
            MyFree((PSTR)pxact);
            /*
             * Locate next apropriate transaction window to get focus.
             */
            if (!pmci->hwndXaction || pmci->hwndXaction == wParam)
                pmci->hwndXaction = GetWindow(hwnd, GW_CHILD);
            if (pmci->hwndXaction == wParam)
                pmci->hwndXaction = GetWindow(wParam, GW_HWNDNEXT);
            if (pmci->hwndXaction == wParam ||
                    !IsWindow(pmci->hwndXaction) ||
                    !IsChild(hwnd, pmci->hwndXaction))
                pmci->hwndXaction = NULL;
            else
                SetFocus(pmci->hwndXaction);
        }
        break;

    case WM_PAINT:
        /*
         * Paint this conversation's related information.
         */
        pmci = (MYCONVINFO *)GetWindowWord(hwnd, 0);
        {
            PAINTSTRUCT ps;
            PSTR psz;

            BeginPaint(hwnd, &ps);
            SetBkMode(ps.hdc, TRANSPARENT);
            psz = pmci->fList ? GetConvListText(pmci->hConv) :
                    GetConvInfoText(pmci->hConv, &pmci->ci);
            if (psz) {
                GetClientRect(hwnd, &rc);
                DrawText(ps.hdc, psz, -1, &rc,
                        DT_WORDBREAK | DT_LEFT | DT_NOPREFIX | DT_TABSTOP);
                MyFree(psz);
            }
            EndPaint(hwnd, &ps);
        }
        break;

    case WM_QUERYENDSESSION:
        return TRUE;

    default:
CallDCP:
        /* Again, since the MDI default behaviour is a little different,
         * call DefMDIChildProc instead of DefWindowProc()
         */
        return DefMDIChildProc (hwnd, msg, wParam, lParam);
    }
    return FALSE;
}


/****************************************************************************
 *                                                                          *
 *  FUNCTION   : Initializemenu ( hMenu )                                   *
 *                                                                          *
 *  PURPOSE    : Sets up greying, enabling and checking of main menu items  *
 *               based on the app's state.                                  *
 *                                                                          *
 ****************************************************************************/
VOID NEAR PASCAL InitializeMenu ( hmenu )
register HANDLE hmenu;
{
    BOOL fLink      = FALSE; // set if Link format is on the clipboard;
    BOOL fAny       = FALSE; // set if hwndActive exists
    BOOL fList      = FALSE; // set if hwndActive is a list window
    BOOL fConnected = FALSE; // set if hwndActive is a connection conversation.
    BOOL fXaction   = FALSE; // set if hwndActive has a selected transaction window
    BOOL fXactions  = FALSE; // set if hwndActive contains transaction windows
    BOOL fBlocked   = FALSE; // set if hwndActive conversation is blocked.
    BOOL fBlockNext = FALSE; // set if handActive conversation is blockNext.
    MYCONVINFO *pmci = NULL;

    if (OpenClipboard(hwndFrame)) {
        fLink = (IsClipboardFormatAvailable(fmtLink));
        CloseClipboard();
    }

    if (fAny = (IsWindow(hwndActive) &&
            (pmci = (MYCONVINFO *)GetWindowWord(hwndActive, 0)))) {
        fXactions = GetWindow(hwndActive, GW_CHILD);
        if (!(fList = pmci->fList)) {
            CONVINFO ci;

            ci.cb = sizeof(CONVINFO);
	    DdeQueryConvInfo(pmci->hConv, (DWORD)QID_SYNC, &ci);
	    fConnected = (BOOL)(ci.wStatus & ST_CONNECTED);
            fXaction = IsWindow(pmci->hwndXaction);
            fBlocked = ci.wStatus & ST_BLOCKED;
            fBlockNext = ci.wStatus & ST_BLOCKNEXT;
        }
    }

    EnableMenuItem(hmenu,   IDM_EDITPASTE,
            fLink           ? MF_ENABLED    : MF_GRAYED);

    // IDM_CONNECTED - always enabled.

    EnableMenuItem(hmenu,   IDM_RECONNECT,
            fList           ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_DISCONNECT,
            fConnected      ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_TRANSACT,
            fConnected      ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem(hmenu,   IDM_ABANDON,
            fXaction        ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem(hmenu,   IDM_ABANDONALL,
            fXactions ? MF_ENABLED : MF_GRAYED);


    EnableMenuItem (hmenu,  IDM_BLOCKCURRENT,
            fConnected && !fBlocked ? MF_ENABLED    : MF_GRAYED);
    CheckMenuItem(hmenu, IDM_BLOCKCURRENT,
            fBlocked        ? MF_CHECKED    : MF_UNCHECKED);

    EnableMenuItem (hmenu,  IDM_ENABLECURRENT,
            fConnected && (fBlocked || fBlockNext) ? MF_ENABLED : MF_GRAYED);
    CheckMenuItem(hmenu,    IDM_ENABLECURRENT,
            !fBlocked       ? MF_CHECKED    : MF_UNCHECKED);

    EnableMenuItem (hmenu,  IDM_ENABLEONECURRENT,
            fConnected && (fBlocked) ? MF_ENABLED : MF_GRAYED);
    CheckMenuItem(hmenu,    IDM_ENABLEONECURRENT,
            fBlockNext      ? MF_CHECKED    : MF_UNCHECKED);

    EnableMenuItem (hmenu,  IDM_BLOCKALLCBS,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_ENABLEALLCBS,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_ENABLEONECB,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem(hmenu,   IDM_BLOCKNEXTCB,
            fAny || fBlockNextCB ? MF_ENABLED    : MF_GRAYED);
    CheckMenuItem(hmenu,    IDM_BLOCKNEXTCB,
            fBlockNextCB    ? MF_CHECKED    : MF_UNCHECKED);

    EnableMenuItem(hmenu,   IDM_TERMNEXTCB,
            fAny || fTermNextCB ? MF_ENABLED    : MF_GRAYED);
    CheckMenuItem(hmenu,    IDM_TERMNEXTCB,
            fTermNextCB     ? MF_CHECKED    : MF_UNCHECKED);

    // IDM_DELAY - always enabled.

    // IDM_TIMEOUT - alwasy enabled.

    EnableMenuItem (hmenu,  IDM_WINDOWTILE,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_WINDOWCASCADE,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_WINDOWICONS,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_WINDOWCLOSEALL,
            fAny            ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_XACTTILE,
            fXactions       ? MF_ENABLED    : MF_GRAYED);

    EnableMenuItem (hmenu,  IDM_XACTCASCADE,
            fXactions       ? MF_ENABLED    : MF_GRAYED);

    CheckMenuItem(hmenu,   IDM_AUTORECONNECT,
            fAutoReconnect  ? MF_CHECKED    : MF_UNCHECKED);

    // IDM_HELPABOUT - always enabled.
}



/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CloseAllChildren ()                                        *
 *                                                                          *
 *  PURPOSE    : Destroys all MDI child windows.                            *
 *                                                                          *
 ****************************************************************************/
VOID NEAR PASCAL CloseAllChildren ()
{
    register HWND hwndT;

    /* hide the MDI client window to avoid multiple repaints */
    ShowWindow(hwndMDIClient,SW_HIDE);

    /* As long as the MDI client has a child, destroy it */
    while ( hwndT = GetWindow (hwndMDIClient, GW_CHILD)){

        /* Skip the icon title windows */
        while (hwndT && GetWindow (hwndT, GW_OWNER))
            hwndT = GetWindow (hwndT, GW_HWNDNEXT);

        if (!hwndT)
            break;

        SendMessage(hwndMDIClient, WM_MDIDESTROY, (WORD)hwndT, 0L);
    }

    ShowWindow( hwndMDIClient, SW_SHOW);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CommandHandler ()                                          *
 *                                                                          *
 *  PURPOSE    : Processes all "frame" WM_COMMAND messages.                 *
 *                                                                          *
 ****************************************************************************/
VOID NEAR PASCAL CommandHandler (
register HWND hwnd,
register WORD wParam)

{
    MYCONVINFO *pmci = NULL;

    if (hwndActive)
        pmci = (MYCONVINFO *)GetWindowWord(hwndActive, 0);

    switch (wParam){
        case IDM_EDITPASTE:
            {
                HANDLE hClipData;
                LPSTR psz;
                XACT xact;

                if (OpenClipboard(hwnd)) {
                    if (hClipData = GetClipboardData(fmtLink)) {
                        if (psz = GlobalLock(hClipData)) {
                            /*
                             * Create a conversation with the link app and
                             * begin a request and advise start transaction.
                             */
                            xact.hConv = CreateConv(DdeCreateStringHandle(idInst, psz, NULL),
                                    DdeCreateStringHandle(idInst, &psz[_fstrlen(psz) + 1], NULL),
                                    FALSE, NULL);
                            if (xact.hConv) {
                                psz += _fstrlen(psz) + 1;
                                psz += _fstrlen(psz) + 1;
                                xact.ulTimeout = DefTimeout;
                                xact.wType = XTYP_ADVSTART;
                                xact.hDdeData = 0;
                                xact.wFmt = CF_TEXT;
                                xact.hszItem = DdeCreateStringHandle(idInst, psz, NULL);
                                xact.fsOptions = 0;
                                ProcessTransaction(&xact);
                                xact.wType = XTYP_REQUEST;
                                ProcessTransaction(&xact);
                            }
                            GlobalUnlock(hClipData);
                        }
                    }
                    CloseClipboard();
                }
            }
            break;

        case IDM_CONNECT:
        case IDM_RECONNECT:
            DoDialog(MAKEINTRESOURCE(IDD_CONNECT), ConnectDlgProc,
                    wParam == IDM_RECONNECT, FALSE);
            break;

        case IDM_DISCONNECT:
            if (hwndActive) {
                SendMessage(hwndMDIClient, WM_MDIDESTROY, (WORD)hwndActive, 0L);
            }
            break;

        case IDM_TRANSACT:
            if (DoDialog(MAKEINTRESOURCE(IDD_TRANSACT), TransactDlgProc,
                    (DWORD)(LPSTR)pmci->hConv, FALSE))
                SetFocus(GetWindow(hwndActive, GW_CHILD));
            break;

        case IDM_ABANDON:
            if (pmci != NULL && IsWindow(pmci->hwndXaction)) {
                DestroyWindow(pmci->hwndXaction);
            }
            break;

        case IDM_ABANDONALL:
            DdeAbandonTransaction(idInst, pmci->hConv, NULL);
            {
                HWND hwndXaction;

                hwndXaction = GetWindow(hwndActive, GW_CHILD);
                while (hwndXaction) {
                    DestroyWindow(hwndXaction);
                    hwndXaction = GetWindow(hwndActive, GW_CHILD);
                }
            }
            break;

        case IDM_BLOCKCURRENT:
            DdeEnableCallback(idInst, pmci->hConv, EC_DISABLE);
            InvalidateRect(hwndActive, NULL, TRUE);
            break;

        case IDM_ENABLECURRENT:
            DdeEnableCallback(idInst, pmci->hConv, EC_ENABLEALL);
            InvalidateRect(hwndActive, NULL, TRUE);
            break;

        case IDM_ENABLEONECURRENT:
            DdeEnableCallback(idInst, pmci->hConv, EC_ENABLEONE);
            InvalidateRect(hwndActive, NULL, TRUE);
            break;

        case IDM_BLOCKALLCBS:
            DdeEnableCallback(idInst, NULL, EC_DISABLE);
            InvalidateRect(hwndMDIClient, NULL, TRUE);
            break;

        case IDM_ENABLEALLCBS:
            DdeEnableCallback(idInst, NULL, EC_ENABLEALL);
            InvalidateRect(hwndMDIClient, NULL, TRUE);
            break;

        case IDM_ENABLEONECB:
            DdeEnableCallback(idInst, NULL, EC_ENABLEONE);
            InvalidateRect(hwndMDIClient, NULL, TRUE);
            break;

        case IDM_BLOCKNEXTCB:
            fBlockNextCB = !fBlockNextCB;
            break;

        case IDM_TERMNEXTCB:
            fTermNextCB = !fTermNextCB;
            break;

        case IDM_DELAY:
            DoDialog(MAKEINTRESOURCE(IDD_VALUEENTRY), DelayDlgProc, NULL,
                    TRUE);
            break;

        case IDM_TIMEOUT:
            DoDialog(MAKEINTRESOURCE(IDD_VALUEENTRY), TimeoutDlgProc, NULL,
                    TRUE);
            break;

        case IDM_CONTEXT:
            DoDialog(MAKEINTRESOURCE(IDD_CONTEXT), ContextDlgProc, NULL, TRUE);
            break;

        case IDM_AUTORECONNECT:
            fAutoReconnect = !fAutoReconnect;
            break;

        /* The following are window commands - these are handled by the
         * MDI Client.
         */
        case IDM_WINDOWTILE:
            /* Tile MDI windows */
            SendMessage (hwndMDIClient, WM_MDITILE, 0, 0L);
            break;

        case IDM_WINDOWCASCADE:
            /* Cascade MDI windows */
            SendMessage (hwndMDIClient, WM_MDICASCADE, 0, 0L);
            break;

        case IDM_WINDOWICONS:
            /* Auto - arrange MDI icons */
            SendMessage (hwndMDIClient, WM_MDIICONARRANGE, 0, 0L);
            break;

        case IDM_WINDOWCLOSEALL:
            CloseAllChildren();
            break;

        case IDM_XACTTILE:
            TileChildWindows(hwndActive);
            break;

        case IDM_XACTCASCADE:
            CascadeChildWindows(hwndActive);
            break;

        case IDM_HELPABOUT:{
            DoDialog(MAKEINTRESOURCE(IDD_ABOUT), AboutDlgProc, NULL, TRUE);
            break;
        }

        default:
           /*
            * This is essential, since there are frame WM_COMMANDS generated
            * by the MDI system for activating child windows via the
            * window menu.
            */
            DefFrameProc(hwnd, hwndMDIClient, WM_COMMAND, wParam, 0L);
    }
}


/****************************************************************************
 *                                                                          *
 *  FUNCTION   : MPError ( hwnd, flags, id, ...)                            *
 *                                                                          *
 *  PURPOSE    : Flashes a Message Box to the user. The format string is    *
 *               taken from the STRINGTABLE.                                *
 *                                                                          *
 *  RETURNS    : Returns value returned by MessageBox() to the caller.      *
 *                                                                          *
 ****************************************************************************/
short FAR CDECL MPError(hwnd, bFlags, id, ...)
HWND hwnd;
WORD bFlags;
WORD id;
{
    char sz[160];
    char szFmt[128];

    LoadString (hInst, id, szFmt, sizeof (szFmt));
    wvsprintf (sz, szFmt, (LPSTR)(&id + 1));
    LoadString (hInst, IDS_APPNAME, szFmt, sizeof (szFmt));
    return MessageBox (hwndFrame, sz, szFmt, bFlags);
}



/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateConv()                                               *
 *                                                                          *
 *  PURPOSE    :                                                            *
 *                                                                          *
 *  RETURNS    :                                                            *
 *                                                                          *
 ****************************************************************************/
HCONV CreateConv(
HSZ hszApp,
HSZ hszTopic,
BOOL fList,
WORD *pError)
{
    HCONV hConv;
    HWND hwndConv = 0;
    CONVINFO ci;

    if (fList) {
        hConv = (HCONV)DdeConnectList(idInst, hszApp, hszTopic, NULL, &CCFilter);
    } else {
        hConv = DdeConnect(idInst, hszApp, hszTopic, &CCFilter);
    }
    if (hConv) {
        if (fList) {
            ci.hszSvcPartner = hszApp;
            ci.hszTopic = hszTopic;
        } else {
            ci.cb = sizeof(CONVINFO);
	    DdeQueryConvInfo(hConv, (DWORD)QID_SYNC, &ci);
        }
        hwndConv = AddConv(ci.hszSvcPartner, ci.hszTopic, hConv, fList);
        // HSZs get freed when window dies.
    }
    if (!hwndConv) {
        if (pError != NULL) {
            *pError = DdeGetLastError(idInst);
        }
        DdeFreeStringHandle(idInst, hszApp);
        DdeFreeStringHandle(idInst, hszTopic);
    }
    return(hConv);
}






/****************************************************************************
 *                                                                          *
 *  FUNCTION   : AddConv()                                                  *
 *                                                                          *
 *  PURPOSE    : Creates an MDI window representing a conversation          *
 *               (fList = FALSE) or a set of MID windows for the list of    *
 *               conversations (fList = TRUE).                              *
 *                                                                          *
 *  EFFECTS    : Sets the hUser for the conversation to the created MDI     *
 *               child hwnd.  Keeps the hszs if successful.                 *
 *                                                                          *
 *  RETURNS    : created MDI window handle.                                 *
 *                                                                          *
 ****************************************************************************/
HWND FAR PASCAL AddConv(
HSZ hszApp,     // these parameters MUST match the MYCONVINFO struct.
HSZ hszTopic,
HCONV hConv,
BOOL fList)
{
    HWND hwnd;
    MDICREATESTRUCT mcs;

    if (fList) {
        /*
         * Create all child windows FIRST so we have info for list window.
         */
        CONVINFO ci;
        HCONV hConvChild = 0;

        ci.cb = sizeof(CONVINFO);
        while (hConvChild = DdeQueryNextServer((HCONVLIST)hConv, hConvChild)) {
	    if (DdeQueryConvInfo(hConvChild, (DWORD)QID_SYNC, &ci)) {
                AddConv(ci.hszSvcPartner, ci.hszTopic, hConvChild, FALSE);
            }
        }
    }

    mcs.szTitle = GetConvTitleText(hConv, hszApp, hszTopic, fList);

    mcs.szClass = fList ? szList : szChild;
    mcs.hOwner  = hInst;
    mcs.x = mcs.cx = CW_USEDEFAULT;
    mcs.y = mcs.cy = CW_USEDEFAULT;
    mcs.style = GetWindow(hwndMDIClient, GW_CHILD) ? 0L : WS_MAXIMIZE;
    mcs.lParam = (DWORD)(LPSTR)&fList - 2;      // -2 for hwndXaction field
    hwnd = (WORD)SendMessage (hwndMDIClient, WM_MDICREATE, 0,
             (LONG)(LPMDICREATESTRUCT)&mcs);

    MyFree((PSTR)(DWORD)mcs.szTitle);

    return hwnd;
}





/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetConvListText()                                          *
 *                                                                          *
 *  RETURN     : Returns a ponter to a string containing a list of          *
 *               conversations contained in the given hConvList freeable    *
 *               by MyFree();                                               *
 *                                                                          *
 ****************************************************************************/
PSTR GetConvListText(
HCONVLIST hConvList)
{
    HCONV hConv = 0;
    WORD cConv = 0;
    CONVINFO ci;
    WORD cb = 0;
    char *psz, *pszStart;

    ci.cb = sizeof(CONVINFO);

    // find out size needed.

    while (hConv = DdeQueryNextServer(hConvList, hConv)) {
	if (DdeQueryConvInfo(hConv, (DWORD)QID_SYNC, &ci)) {
            if (!IsWindow((HWND)ci.hUser)) {
                if (ci.wStatus & ST_CONNECTED) {
                    /*
                     * This conversation doesn't have a corresponding
                     * MDI window.  This is probably due to a reconnection.
                     */
                    ci.hUser = AddConv(ci.hszSvcPartner, ci.hszTopic, hConv, FALSE);
                } else {
                    continue;   // skip this guy - he was closed locally.
                }
            }
            cb += GetWindowTextLength((HWND)ci.hUser);
            if (cConv++)
                cb += 2;        // room for CRLF
        }
    }
    cb++;                       // for terminator.

    // allocate and fill

    if (pszStart = psz = MyAlloc(cb)) {
        *psz = '\0';
        hConv = 0;
        while (hConv = DdeQueryNextServer(hConvList, hConv)) {
	    if (DdeQueryConvInfo(hConv, (DWORD)QID_SYNC, &ci) &&
                    IsWindow((HWND)ci.hUser)) {
                psz += GetWindowText((HWND)ci.hUser, psz, cb);
                if (--cConv) {
                    *psz++ = '\r';
                    *psz++ = '\n';
                }
            }
        }
    }
    return(pszStart);
}


/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetConvInfoText()                                          *
 *                                                                          *
 *  PURPOSE    : Returns a pointer to a string that reflects a              *
 *               conversation's information.  Freeable by MyFree();         *
 *                                                                          *
 ****************************************************************************/
PSTR GetConvInfoText(
HCONV hConv,
CONVINFO *pci)
{
    PSTR psz;
    PSTR szApp;

    psz = MyAlloc(300);
    pci->cb = sizeof(CONVINFO);
    if (hConv) {
	if (!DdeQueryConvInfo(hConv, (DWORD)QID_SYNC, (PCONVINFO)pci)) {
            strcpy(psz, "State=Disconnected");
            return(psz);
        }
        szApp = GetHSZName(pci->hszServiceReq);
        wsprintf(psz,
                "hUser=0x%lx\r\nhConvPartner=0x%lx\r\nhszServiceReq=%s\r\nStatus=%s\r\nState=%s\r\nLastError=%s",
                pci->hUser, pci->hConvPartner, (LPSTR)szApp,
                (LPSTR)Status2String(pci->wStatus),
                (LPSTR)State2String(pci->wConvst),
                (LPSTR)Error2String(pci->wLastError));
        MyFree(szApp);
    } else {
        strcpy(psz, Error2String(DdeGetLastError(idInst)));
    }
    return(psz);
}



/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetConvTitleText()                                         *
 *                                                                          *
 *  PURPOSE    : Creates standard window title text based on parameters.    *
 *                                                                          *
 *  RETURNS    : psz freeable by MyFree()                                   *
 *                                                                          *
 ****************************************************************************/
PSTR GetConvTitleText(
HCONV hConv,
HSZ hszApp,
HSZ hszTopic,
BOOL fList)
{
    WORD cb;
    PSTR psz;

    cb = (WORD)DdeQueryString(idInst, hszApp, NULL, 0, 0) +
            (WORD)DdeQueryString(idInst, hszTopic, (LPSTR)NULL, 0, 0) +
            (fList ? 30 : 20);

    if (psz = MyAlloc(cb)) {
        DdeQueryString(idInst, hszApp, psz, cb, 0);
        strcat(psz, "|");
        DdeQueryString(idInst, hszTopic, &psz[strlen(psz)], cb, 0);
        if (fList)
            strcat(psz, " - LIST");
        wsprintf(&psz[strlen(psz)], " - (%lx)", hConv);
    }
    return(psz);
}



/****************************************************************************
 *                                                                          *
 *  FUNCTION   : Status2String()                                            *
 *                                                                          *
 *  PURPOSE    : Converts a conversation status word to a string and        *
 *               returns a pointer to that string.  The string is valid     *
 *               till the next call to this function.                       *
 *                                                                          *
 ****************************************************************************/
PSTR Status2String(
WORD status)
{
    WORD c, i;
    static char szStatus[6 * 18];
    static struct {
        char *szStatus;
        WORD status;
    } s2s[] = {
        { "Connected"    ,   ST_CONNECTED },
        { "Advise"       ,   ST_ADVISE },
        { "IsLocal"      ,   ST_ISLOCAL },
        { "Blocked"      ,   ST_BLOCKED },
        { "Client"       ,   ST_CLIENT },
        { "Disconnected" ,   ST_TERMINATED },
        { "BlockNext"    ,   ST_BLOCKNEXT },
    };
#define CFLAGS 7
    szStatus[0] = '\0';
    c = 0;
    for (i = 0; i < CFLAGS; i++) {
        if (status & s2s[i].status) {
            if (c++)
                strcat(szStatus, " | ");
            strcat(szStatus, s2s[i].szStatus);
        }
    }
    return szStatus;
#undef CFLAGS
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : State2String()                                             *
 *                                                                          *
 *  PURPOSE    : converts a conversation state word to a string and         *
 *               returns a pointer to that string.  The string is valid     *
 *               till the next call to this routine.                        *
 *                                                                          *
 ****************************************************************************/
PSTR State2String(
WORD state)
{
    static char *s2s[] = {
        "NULL"             ,
        "Incomplete"       ,
        "Standby"          ,
        "Initiating"       ,
        "ReqSent"          ,
        "DataRcvd"         ,
        "PokeSent"         ,
        "PokeAckRcvd"      ,
        "ExecSent"         ,
        "ExecAckRcvd"      ,
        "AdvSent"          ,
        "UnadvSent"        ,
        "AdvAckRcvd"       ,
        "UnadvAckRcvd"     ,
        "AdvDataSent"      ,
        "AdvDataAckRcvd"   ,
        "?"                ,    // 16
    };

    if (state >= 17)
        return s2s[17];
    else
        return s2s[state];
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : Error2String()                                             *
 *                                                                          *
 *  PURPOSE    : Converts an error code to a string and returns a pointer   *
 *               to that string.  The string is valid until the next call   *
 *               to this function.                                          *
 *                                                                          *
 ****************************************************************************/
PSTR Error2String(
WORD error)
{
    static char szErr[23];
    static char *e2s[] = {
        "Advacktimeout"              ,
        "Busy"                       ,
        "Dataacktimeout"             ,
        "Dll_not_initialized"        ,
        "Dll_usage"                  ,
        "Execacktimeout"             ,
        "Invalidparameter"           ,
        "Low Memory warning"         ,
        "Memory_error"               ,
        "Notprocessed"               ,
        "No_conv_established"        ,
        "Pokeacktimeout"             ,
        "Postmsg_failed"             ,
        "Reentrancy"                 ,
        "Server_died"                ,
        "Sys_error"                  ,
        "Unadvacktimeout"            ,
        "Unfound_queue_id"           ,
    };
    if (!error) {
        strcpy(szErr, "0");
    } else if (error > DMLERR_LAST || error < DMLERR_FIRST) {
        strcpy(szErr, "???");
    } else {
        strcpy(szErr, e2s[error - DMLERR_FIRST]);
    }
    return(szErr);
}





/****************************************************************************
 *                                                                          *
 *  FUNCTION   : Type2String()                                              *
 *                                                                          *
 *  PURPOSE    : Converts a wType word and fsOption flags to a string and   *
 *               returns a pointer to that string.  the string is valid     *
 *               until the next call to this function.                      *
 *                                                                          *
 ****************************************************************************/
PSTR Type2String(
WORD wType,
WORD fsOptions)
{
    static char sz[30];
    static char o2s[] = "^!#$X*<?";
    static char *t2s[] = {
        ""                 ,
        "AdvData"          ,
        "AdvReq"           ,
        "AdvStart"         ,
        "AdvStop"          ,
        "Execute"          ,
        "Connect"          ,
        "ConnectConfirm"   ,
        "XactComplete"    ,
        "Poke"             ,
        "Register"         ,
        "Request"          ,
        "Term"             ,
        "Unregister"       ,
        "WildConnect"      ,
        ""                 ,
    };
    WORD bit, c, i;

    strcpy(sz, t2s[((wType & XTYP_MASK) >> XTYP_SHIFT)]);
    c = strlen(sz);
    sz[c++] = ' ';
    for (i = 0, bit = 1; i < 7; bit = bit << 1, i++) {
        if (fsOptions & bit)
            sz[c++] = o2s[i];
    }
    sz[c] = '\0';
    return(sz);
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetHSZName()                                               *
 *                                                                          *
 *  PURPOSE    : Allocates local memory for and retrieves the string form   *
 *               of an HSZ.  Returns a pointer to the local memory or NULL  *
 *               if failure.  The string must be freed via MyFree().        *
 *                                                                          *
 ****************************************************************************/
PSTR GetHSZName(
HSZ hsz)
{
    PSTR psz;
    WORD cb;

    cb = (WORD)DdeQueryString(idInst, hsz, NULL, 0, 0) + 1;
    psz = MyAlloc(cb);
    DdeQueryString(idInst, hsz, psz, cb, 0);
    return(psz);
}


/****************************************************************************
 *
 *  FUNCTION   : MyMsgFilterProc
 *
 *  PURPOSE    : This filter proc gets called for each message we handle.
 *               This allows our application to properly dispatch messages
 *               that we might not otherwise see because of DDEMLs modal
 *               loop that is used while processing synchronous transactions.
 *
 *               Generally, applications that only do synchronous transactions
 *               in response to user input (as this app does) does not need
 *               to install such a filter proc because it would be very rare
 *               that a user could command the app fast enough to cause
 *               problems.  However, this is included as an example.
 *
 ****************************************************************************/
DWORD FAR PASCAL __export MyMsgFilterProc(
int nCode,
WORD wParam,
DWORD lParam)
{
    wParam; // not used

#define lpmsg ((LPMSG)lParam)
    if (nCode == MSGF_DDEMGR) {

        /* If a keyboard message is for the MDI , let the MDI client
         * take care of it.  Otherwise, check to see if it's a normal
         * accelerator key.  Otherwise, just handle the message as usual.
         */

        if ( !TranslateMDISysAccel (hwndMDIClient, lpmsg) &&
             !TranslateAccelerator (hwndFrame, hAccel, lpmsg)){
            TranslateMessage (lpmsg);
            DispatchMessage (lpmsg);
        }
        return(1);
    }
    return(0);
#undef lpmsg
}
