
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1993-1996 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/***************************************************************************
 *                                                                         *
 *  MODULE      : dde.c                                                    *
 *                                                                         *
 *  PURPOSE     : Contains routines for handling of DDE interaction with   *
 *                DDEML.                                                   *
 *                                                                         *
 ***************************************************************************/
#include "client.h"
#include <string.h>
#include <memory.h>
#include "infoctrl.h"
#include "huge.h"

TCHAR szT[100];

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateXactionWindow()                                      *
 *                                                                          *
 *  PURPOSE    : Creates a transaction window for the given transaction     *
 *               under the given conversation window.                       *
 *                                                                          *
 *  RETURNS    : TRUE  - If successful.                                     *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
HWND CreateXactionWindow(
HWND hwndMDI,
XACT *pxact)
{
    PTSTR pszFmt, pszItem;
    PTSTR pData;
    HWND hwnd;

    pszItem = GetHSZName(pxact->hszItem);
    pszFmt = GetFormatName(pxact->wFmt);
    pData = GetTextData(pxact->hDdeData);

    /*
     *   Útype/optsÄÄÄÄÄÄÄ ITEM ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄretÄ¿    GWL_USER=pxact
     *   ³                                         ³
     *   ³                                         ³
     *   ³                                         ³
     *   ³                                         ³
     *   ³                 DATA                    ³
     *   ³                                         ³
     *   ³                                         ³
     *   ³                                         ³
     *   ³                                         ³
     *   Àstate/errorÄÄÄÄÄ FORMAT ÄÄÄÄÄÄÄÄÄÄResultÄÙ
     */
    hwnd = CreateInfoCtrl((LPTSTR)pData,
            (INT)SendMessage(hwndMDI, UM_GETNEXTCHILDX, 0, 0L),
            (INT)SendMessage(hwndMDI, UM_GETNEXTCHILDY, 0, 0L),
            200, 100, hwndMDI, hInst,
            Type2String(pxact->wType, pxact->fsOptions), pszItem, NULL,
            TEXT("Starting"), (LPTSTR)pszFmt, NULL,
            ICSTY_SHOWFOCUS, 0, (DWORD)(LPTSTR)pxact);
    MyFree(pszItem);
    MyFree(pszFmt);
    MyFree(pData);
    return(hwnd);
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : ProcessTransaction()                                       *
 *                                                                          *
 *  PURPOSE    : Processes synchronous transactions entirely and starts     *
 *               async transactions.  Transaction attempts result in a      *
 *               transaction window being created which displays the state  *
 *               or results of the transaction.  (the callback function     *
 *               updates these windows as it gets calls) Transaction        *
 *               windows stay around until abandoned by the user or until   *
 *               the conversation is disconnected.  Advise Data and Advise  *
 *               Stop transactions are special.  We don't create a new      *
 *               window if the associated advise start transaction window   *
 *               can be found.                                              *
 *                                                                          *
 *  RETURNS    : TRUE  - If successful.                                     *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
BOOL ProcessTransaction(
XACT *pxact)
{
    CONVINFO ci;
    HWND hwndInfoCtrl = 0;

    /* create transaction window to show we tried (except in ADVSTOP case) */

    pxact = (XACT *)memcpy(MyAlloc(sizeof(XACT)), (PTSTR)pxact, sizeof(XACT));
    ci.cb = sizeof(CONVINFO);
    DdeQueryConvInfo(pxact->hConv, QID_SYNC, &ci); // ci.hUser==hConv
    if (pxact->wType == XTYP_ADVSTOP) {
        hwndInfoCtrl = FindAdviseChild((HWND)ci.hUser, pxact->hszItem,
                pxact->wFmt);
        if (hwndInfoCtrl) {
            SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UL,
                    (DWORD)(LPTSTR)Type2String(pxact->wType, pxact->fsOptions));
            DdeFreeStringHandle(idInst, pxact->hszItem);
        }
    }
    /*
     * If we still need to create a transaction window, do so here.
     */
    if (!hwndInfoCtrl) {
        hwndInfoCtrl = CreateXactionWindow((HWND)ci.hUser, pxact);
        if (!hwndInfoCtrl) {
            MyFree(pxact);
            return 0;
        }
        SetFocus(hwndInfoCtrl);
    }
    /*
     * Disable callbacks for this conversation now if the XOPT_DISABLEFIRST
     * option is set.  This tests disabling asynchronous transactions
     * before they are completed.
     */
    if (pxact->fsOptions & XOPT_DISABLEFIRST)
        DdeEnableCallback(idInst, pxact->hConv, EC_DISABLE);
    /*
     * Adjust the timeout for asynchronous transactions.
     */
    if (pxact->fsOptions & XOPT_ASYNC)
        pxact->ulTimeout = TIMEOUT_ASYNC;

    /*
     * start transaction with DDEML here
     */
    pxact->ret = (DWORD) DdeClientTransaction((LPBYTE)pxact->hDdeData, 0xFFFFFFFF,
            pxact->hConv, pxact->hszItem, pxact->wFmt,
            pxact->wType,
            pxact->ulTimeout, (LPDWORD)&pxact->Result);

    /*
     * show return value in transaction window
     */
    wsprintf(szT, TEXT("ret=%lx"), pxact->ret);
    SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UR, (DWORD)(LPTSTR)szT);

    /*
     * show result or ID value in transaction window
     */
    wsprintf(szT, pxact->fsOptions & XOPT_ASYNC ? TEXT("ID=%ld") :
            TEXT("result=0x%lx"), pxact->Result);
    SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LR, (DWORD)(LPTSTR)szT);

    if ((pxact->fsOptions & XOPT_ASYNC) && pxact->ret) {
        /*
         * asynchronous successful start - link transaction to window.
         */
        DdeSetUserHandle(pxact->hConv, pxact->Result, (DWORD)hwndInfoCtrl);

        /*
         * Abandon started async transaction after initiated if
         * XOPT_ABANDONAFTERSTART is chosen.  This tests the mid-transaction
         * abandoning code.
         */
        if (pxact->fsOptions & XOPT_ABANDONAFTERSTART)
            DdeAbandonTransaction(idInst, pxact->hConv, pxact->Result);
        /*
         * show actual status
         */
        ci.cb = sizeof(CONVINFO);
        DdeQueryConvInfo(pxact->hConv, pxact->Result, &ci);
        SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL,
                (DWORD)(LPTSTR)State2String(ci.wConvst));
    } else {
        /*
         * Synchronous transactions are completed already so pass on to
         * CompleteTransaction right away.
         */
        CompleteTransaction(hwndInfoCtrl, pxact);
    }
    return TRUE;
}





/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CompleteTransaction()                                      *
 *                                                                          *
 *  PURPOSE    : This handles completed synchronous and asynchronous        *
 *               transactions as well as failed attempted transactions.     *
 *                                                                          *
 *  RETURNS    : TRUE  - If successful.                                     *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
VOID CompleteTransaction(
HWND hwndInfoCtrl,
XACT *pxact)
{
    PTSTR psz;

    if (pxact->ret) {
        /*
         * Successful transaction case
         */
        SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL,
                (DWORD)(LPTSTR)TEXT("Completed"));

        if (pxact->wType == XTYP_REQUEST) {
            /*
             * Show resulting data
             */
            psz = GetTextData((HDDEDATA)pxact->ret);
            if (!DdeCmpStringHandles(pxact->hszItem, hszHuge) &&
                    !CheckHugeData((HDDEDATA)pxact->ret)) {
                SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_CENTER,
                        (DWORD)(LPTSTR)TEXT("Invalid Huge Data"));
            } else {
                SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_CENTER,
                        (DWORD)(LPTSTR)psz);
            }
            MyFree(psz);
            /*
             * free returned data since it is displayed.
             */
            DdeFreeDataHandle((HDDEDATA)pxact->ret);
            pxact->ret = 0L;
            SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UR, 0);
        }
    } else {
        /*
         * failed - show error result.
         */
        SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL,
                (DWORD)(LPTSTR)Error2String(DdeGetLastError(idInst)));
    }
    pxact->fsOptions |= XOPT_COMPLETED;
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DdeCallback()                                              *
 *                                                                          *
 *  PURPOSE    : This handles all callbacks from the DDEML.  This handles   *
 *               updating of the associated conversation and any special    *
 *               testing cases such as blocking callbacks etc.              *
 *                                                                          *
 *               For the most part, clients only handle advise data and     *
 *               asynchronous transaction completion here.                  *
 *                                                                          *
 *  RETURNS    : Results vary depending on transaction type.                *
 *                                                                          *
 ****************************************************************************/
HDDEDATA CALLBACK DdeCallback(
UINT wType,
UINT wFmt,
HCONV hConv,
HSZ hsz1,
HSZ hsz2,
HDDEDATA hData,
DWORD lData1,
DWORD lData2)
{
    HWND hwnd;
    CONVINFO ci;
    XACT *pxact;

    if (hConv) {
        /*
         * update conversation status if it changed.
         */
        MYCONVINFO *pmci;

        ci.cb = sizeof(CONVINFO);
        if (!DdeQueryConvInfo(hConv, QID_SYNC, &ci) || (!IsWindow((HWND)ci.hUser))) {
            /*
             * This conversation does not yet have a corresponding MDI window
             * or is disconnected.
             */
            return 0;
        }
        if (pmci = (MYCONVINFO *)GetWindowLong((HWND)ci.hUser, 0)) {
            if (pmci->ci.wStatus != ci.wStatus ||
                    pmci->ci.wConvst != ci.wConvst ||
                    pmci->ci.wLastError != ci.wLastError) {
                /*
                 * Things have changed, updated the conversation window.
                 */
                InvalidateRect((HWND)ci.hUser, NULL, TRUE);
            }
            if (ci.wConvst & ST_INLIST) {
                /*
                 * update the associated list window (if any) as well.
                 */
                if (hwnd = FindListWindow(ci.hConvList))
                    InvalidateRect(hwnd, NULL, TRUE);
            }
        }
    }

    /*
     * handle special block on next callback option here.  This demonstrates
     * the CBR_BLOCK feature.
     */
    if (fBlockNextCB && !(wType & XTYPF_NOBLOCK)) {
        fBlockNextCB = FALSE;
        return(CBR_BLOCK);
    }

    /*
     * handle special termination here.  This demonstrates that at any time
     * a client can drop a conversation.
     */
    if (fTermNextCB && hConv && wType != XTYP_DISCONNECT) {
        fTermNextCB = FALSE;
        MyDisconnect(hConv);
        return(0);
    }

    /*
     * Now we begin sort out what to do.
     */
    switch (wType) {
    case XTYP_REGISTER:
    case XTYP_UNREGISTER:
        /*
         * This is where the client would insert code to keep track of
         * what servers are available.  This could cause the initiation
         * of some conversations.
         */
        break;

    case XTYP_DISCONNECT:
        if (fAutoReconnect) {
            /*
             * attempt a reconnection
             */
            if (hConv = DdeReconnect(hConv)) {
                AddConv(ci.hszServiceReq, ci.hszTopic, hConv, FALSE);
                return 0;
            }
        }

        /*
         * update conv window to show its new state.
         */
        SendMessage((HWND)ci.hUser, UM_DISCONNECTED, 0, 0);
        return 0;
        break;

    case XTYP_ADVDATA:
        /*
         * data from an active advise loop (from a server)
         */
        Delay(wDelay);
        hwnd = FindAdviseChild((HWND)ci.hUser, hsz2, wFmt);
        if (!IsWindow(hwnd)) {
            PTSTR pszItem, pszFmt;
            /*
             * AdviseStart window is gone, make a new one.
             */
            pxact = (XACT *)MyAlloc(sizeof(XACT));
            pxact->wType = wType;
            pxact->hConv = hConv;
            pxact->wFmt = wFmt;
            pxact->hszItem = hsz2;
            DdeKeepStringHandle(idInst, hsz2);

            pszItem = GetHSZName(hsz2);
            pszFmt = GetFormatName(wFmt);

            hwnd = CreateInfoCtrl(NULL,
                    (INT)SendMessage((HWND)ci.hUser, UM_GETNEXTCHILDX, 0, 0L),
                    (INT)SendMessage((HWND)ci.hUser, UM_GETNEXTCHILDY, 0, 0L),
                    200, 100,
                    (HWND)ci.hUser, hInst,
                    Type2String(wType, 0), (LPTSTR)pszItem, NULL,
                    NULL, (LPTSTR)pszFmt, NULL,
                    ICSTY_SHOWFOCUS, 0, (DWORD)(LPTSTR)pxact);

            MyFree(pszFmt);
            MyFree(pszItem);

            if (!IsWindow(hwnd))
                return(DDE_FNOTPROCESSED);
        }
        if (!hData) {
            /*
             * XTYPF_NODATA case - request the info. (we do this synchronously
             * for simplicity)
             */
            hData = DdeClientTransaction(NULL, 0L, hConv, hsz2, wFmt,
                    XTYP_REQUEST, DefTimeout, NULL);
        }
        if (hData) {
            PTSTR pData;
            /*
             * Show incomming data on corresponding transaction window.
             */
            pData = GetTextData(hData);
            SendMessage(hwnd, ICM_SETSTRING, ICSID_CENTER, (DWORD)(LPTSTR)pData);
            MyFree(pData);
            DdeFreeDataHandle(hData);
        }
        SendMessage(hwnd, ICM_SETSTRING, ICSID_LL, (DWORD)(LPTSTR)TEXT("Advised"));
        return((HDDEDATA)DDE_FACK);
        break;

    case XTYP_XACT_COMPLETE:
        /*
         * An asynchronous transaction has completed.  Show the results.
         *
         * ...unless the XOPT_BLOCKRESULT is chosen.
         */

        ci.cb = sizeof(CONVINFO);
        if (DdeQueryConvInfo(hConv, lData1, &ci) &&
                IsWindow((HWND)ci.hUser) &&
                (pxact = (XACT *)GetWindowLong((HWND)ci.hUser, GWL_USER))) {

            if (pxact->fsOptions & XOPT_BLOCKRESULT) {
                pxact->fsOptions &= ~XOPT_BLOCKRESULT;
                return(CBR_BLOCK);
            }

            pxact->Result = lData2;
            pxact->ret = (DWORD)hData;
            CompleteTransaction((HWND)ci.hUser, pxact);
        }
        break;
    }
}







/****************************************************************************
 *                                                                          *
 *  FUNCTION   : FindAdviseChild()                                          *
 *                                                                          *
 *  PURPOSE    : Search through the child windows of hwndMDI for an info    *
 *               ctrl that has the same Item and format and is an           *
 *               ADVSTART ADVSTOP or ADVDATA transaction window.            *
 *                                                                          *
 *               We use these to show the associated advise data.           *
 *                                                                          *
 *  RETURNS    : The transaction window handle or 0 on failure.             *
 *                                                                          *
 ****************************************************************************/
HWND FindAdviseChild(
HWND hwndMDI,
HSZ hszItem,
DWORD wFmt)
{
    HWND hwnd, hwndStart;
    XACT *pxact;

    if (!IsWindow(hwndMDI))
        return 0;

    hwnd = hwndStart = GetWindow(hwndMDI, GW_CHILD);
    while (hwnd && IsChild(hwndMDI, hwnd)) {
        pxact = (XACT *)GetWindowLong(hwnd, GWL_USER);
        if (pxact &&
                (pxact)->wFmt == wFmt &&
                (pxact)->hszItem == hszItem &&
                (
                    ((pxact->wType & XTYP_ADVSTART) == XTYP_ADVSTART) ||
                    (pxact->wType == XTYP_ADVSTOP) ||
                    (pxact->wType == XTYP_ADVDATA)
                )
           ) {
            return(hwnd);
        }
        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
        if (hwnd == hwndStart)
            return 0;
    }
    return 0;
}



/****************************************************************************
 *                                                                          *
 *  FUNCTION   : FindListWindow()                                           *
 *                                                                          *
 *  PURPOSE    : Locates the list window associated with this conversation  *
 *               list.                                                      *
 *                                                                          *
 *  RETURNS    : The window handle of the list window or 0 on failure.      *
 *                                                                          *
 ****************************************************************************/
HWND FindListWindow(
HCONVLIST hConvList)
{
    HWND hwnd;
    MYCONVINFO *pmci;

    hwnd = GetWindow(hwndMDIClient, GW_CHILD);
    while (hwnd) {
        if (GetWindowLong(hwnd, GWL_WNDPROC) == (LONG)MDIChildWndProc) {
            pmci = (MYCONVINFO *)GetWindowLong(hwnd, 0);
            if (pmci != NULL && pmci->fList &&
                    pmci->hConv == (HCONV)hConvList)
                return(hwnd);
        }
        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
    }
    return 0;
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetTextData()                                              *
 *                                                                          *
 *  PURPOSE    : Allocates and returns a pointer to the data contained in   *
 *               hData.  This assumes that hData points to text data and    *
 *               will properly handle huge text data by leaving out the     *
 *               middle of the string and placing the size of the string    *
 *               into this string portion.                                  *
 *                                                                          *
 *  RETURNS    : A pointer to the allocated string.                         *
 *                                                                          *
 ****************************************************************************/
PTSTR GetTextData(
HDDEDATA hData)
{
    PTSTR psz;
    DWORD cb;

#define MAXCCH  1024

    if (hData == 0) {
        return(NULL);
    }

    cb = DdeGetData(hData, NULL, 0, 0);
    if (!hData || !cb)
        return NULL;

    if (cb > MAXCCH) {                // possibly HUGE object!
        psz = MyAlloc(MAXCCH * sizeof(TCHAR));
        DdeGetData(hData, (PBYTE)psz, MAXCCH - 46 * sizeof(TCHAR), 0L);
        wsprintf(&psz[MAXCCH - 46], TEXT("<---Size=%ld"), cb);
    } else {
        psz = MyAlloc((DWORD)cb);
        DdeGetData(hData, (LPBYTE)psz, cb, 0L);
    }
    return psz;
#undef MAXCCH
}







/****************************************************************************
 *                                                                          *
 *  FUNCTION   : MyGetClipboardFormatName()                                 *
 *                                                                          *
 *  PURPOSE    : Properly retrieves the string associated with a clipboard  *
 *               format.  If the format does not have a string associated   *
 *               with it, the string #dddd is returned.                     *
 *                                                                          *
 *  RETURNS    : The number of characters copied into lpstr or 0 on error.  *
 *                                                                          *
 ****************************************************************************/
INT MyGetClipboardFormatName(
DWORD fmt,
LPTSTR lpstr,
INT cbMax)
{
    if (fmt < 0xc000) {
        // predefined or integer format - just get the atom string
        // wierdly enough, GetClipboardFormatName() doesn't support this.
        return(GlobalGetAtomName((ATOM)fmt, lpstr, cbMax));
    } else {
        return(GetClipboardFormatName(fmt, lpstr, cbMax));
    }
}





/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetFormatName()                                            *
 *                                                                          *
 *  PURPOSE    : allocates and returns a pointer to a string representing   *
 *               a format.  Use MyFree() to free this string.               *
 *                                                                          *
 *  RETURNS    : The number of characters copied into lpstr or 0 on error.  *
 *                                                                          *
 ****************************************************************************/
PTSTR GetFormatName(
DWORD wFmt)
{
    PTSTR psz;
    DWORD cb;

    if (wFmt == CF_UNICODETEXT) {
       psz = MyAlloc(40);
       _tcscpy(psz, TEXT("CF_UNICODETEXT"));
       return psz;
    }
    if (wFmt == CF_TEXT) {
        psz = MyAlloc(40);
        _tcscpy(psz, TEXT("CF_TEXT"));
        return psz;
    }
    psz = MyAlloc(255);
    *psz = TEXT('\0');
    cb = GetClipboardFormatName(wFmt, psz, 255) + 1;
    return((PTSTR)LocalReAlloc((HANDLE)psz, cb, LMEM_MOVEABLE));
}




/****************************************************************************
 *                                                                          *
 *  FUNCTION   : MyDisconnect()                                             *
 *                                                                          *
 *  PURPOSE    : Disconnects the given conversation after updating the      *
 *               associated conversation window.                            *
 *                                                                          *
 *  RETURNS    : TRUE on success, FALSE on failuer.                         *
 *                                                                          *
 ****************************************************************************/
BOOL MyDisconnect(
HCONV hConv)
{
    CONVINFO ci;
    HWND hwnd;
    // before we disconnect, invalidate the associated list window - if
    // applicable.

    ci.cb = sizeof(CONVINFO);

    if (DdeQueryConvInfo(hConv, QID_SYNC, &ci) && ci.hConvList &&
            (hwnd = FindListWindow(ci.hConvList)))
        InvalidateRect(hwnd, NULL, TRUE);
    return(DdeDisconnect(hConv));
}
