
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1995 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.
\******************************************************************************/

/*****************************************************************************\
*
* DDEMO.C
*
* This file implements a simple DDEML sample application that demonstrates
* some of the ways the DDEML APIs can be used.
*
* Each instance of this application becomes both a DDE client and a DDE
* server with any other instances of this application that are found.
*
* Since it assumes it is talking to itself, this program takes some liberties
* to simplify things.  For instance, this application does not support the
* standard SysTopic topic and does not use any standard formats.
*
* The basic concepts this application will show you are:
*
*   How to use lists of conversations properly
*   How to handle links
*   How to handle simple asynchronous transactions
*   How to use your own custom formats
*
\*****************************************************************************/
#include <windows.h>
#include <ddeml.h>
//#ifdef UNICODE
//#include <wchar.h>
//#endif // UNICODE
#include <stdlib.h>
#include <string.h>

#ifdef UNICODE
#define STRICMP wcsicmp
#define ITOA(c, sz, b) (itoa(sizeof(szA), szA, b), mbstowcs(sz, szA, b), sz)
#else
#define STRICMP stricmp
#define ITOA itoa
#endif  // UNICODE

HDDEDATA CALLBACK DdeCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hszTopic,
        HSZ hszItem, HDDEDATA hData, DWORD lData1, DWORD lData2);
VOID PaintDemo(HWND hwnd);
LONG  APIENTRY MainWndProc(HWND hwnd, UINT message, WPARAM wParam,
        LONG lParam);
VOID BroadcastTransaction(PBYTE pSrc,DWORD cbData,UINT fmt,UINT xtyp);

/*
 * Define this value to limit how fast data changes.  If we just let data
 * change as fast a possible, we might bog down the system with DDE
 * messages.
 */
#define BASE_TIMEOUT 100

BOOL        fActive;                    // indicates data is changing
DWORD       idInst = 0;                 // our DDEML instance object
HANDLE      hInst;                      // our instance/module handle
HCONVLIST   hConvList = 0;              // the list of all convs we have open
HSZ         hszAppName = 0;             // the generic hsz for everything
HWND        hwndMain;                   // our main window handle
TCHAR       szT[20];                    // static buffer for painting
#ifdef UNICODE
CHAR        szA[20];                    // static buffer for UNICODE conversion
TCHAR       szTitle[] = TEXT("DDEmo (U)");
#else
TCHAR       szTitle[] = TEXT("DDEmo");
#endif
TCHAR       szApp[] = TEXT("DDEmo");    // DDE service name
TCHAR       szPause[] = TEXT("PAUSE");  // DDE Execute command
TCHAR       szResume[] = TEXT("RESUME");// DDE Execute command
UINT        OurFormat;                  // our custom registered format
int         InCount = 0;                // static buffer to hold incomming data
int         cConvs = 0;                 // number of active conversations
int         count = 0;                  // our data
int         cyText, cxText, cyTitle;    // sizes for painting

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
INT nCmdShow)
{
    MSG msg;
    WNDCLASS  wc;
    TEXTMETRIC metrics;
    HDC hdc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = szTitle;

    if (!RegisterClass(&wc))
        return(FALSE);

    /*
     * Here we tell DDEML what we will be doing.
     *
     * 1) We let it know our callback proc address - MakeProcInstance
     *      is called just to be more portable.
     * 2) Filter-inits - don't accept any WM_DDE_INITIATE messages for
     *      anything but our registered service name.
     * 3) Don't bother to notify us of confirmed connections
     * 4) Don't allow connections with ourselves.
     * 5) Don't bother us with XTYP_POKE transactions.
     */
    if (DdeInitialize(&idInst,
            (PFNCALLBACK)MakeProcInstance((FARPROC)DdeCallback, hInstance),
            APPCMD_FILTERINITS |
            CBF_SKIP_CONNECT_CONFIRMS |
            CBF_FAIL_SELFCONNECTIONS |
            CBF_FAIL_POKES,
            0))
        return(FALSE);

    hInst = hInstance;
    hwndMain = CreateWindow(
        szTitle,
        szTitle,
        WS_CAPTION | WS_BORDER | WS_SYSMENU,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        0,
        0,
        NULL,
        NULL,
        hInstance,
        NULL
    );

    if (!hwndMain) {
        DdeUninitialize(idInst);
        return(FALSE);
    }

    hdc = GetDC(hwndMain);
    GetTextMetrics(hdc, &metrics);
    cyText = metrics.tmHeight + metrics.tmExternalLeading;
    cxText = metrics.tmMaxCharWidth * 8;
    cyTitle = GetSystemMetrics(SM_CYCAPTION);
    ReleaseDC(hwndMain, hdc);

     /*
     * Initialize all our string handles for lookups later
     */
    hszAppName = DdeCreateStringHandle(idInst, szApp, 0);
    /*
     * Register our formats
     */
    OurFormat = RegisterClipboardFormat(szApp);
    /*
     * Register our service -
     *  This will cause DDEML to notify DDEML clients about the existance
     *  of a new DDE service.
     */
    DdeNameService(idInst, hszAppName, 0, DNS_REGISTER);
    /*
     * Connect to any other instances of ourselves that may already be
     * running.
     */
    hConvList = DdeConnectList(idInst, hszAppName, hszAppName, hConvList, NULL);
    BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART);

    SetWindowPos(hwndMain, 0, 0, 0, cxText,
                (cyText * (cConvs + 1)) + cyTitle, SWP_NOMOVE | SWP_NOZORDER);
    ShowWindow(hwndMain, nCmdShow);
    UpdateWindow(hwndMain);

    while (GetMessage(&msg, 0, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    DestroyWindow(hwndMain);
    UnregisterClass(szTitle, hInstance);
    return(FALSE);
}


/*
 * BroadcastTransaction
 *
 * Does the specified transaction on all conversations in hConvList
 */
VOID BroadcastTransaction(
PBYTE pSrc,
DWORD cbData,
UINT fmt,
UINT xtyp)
{
    HCONV hConv;
    DWORD dwResult;
    int cConvsOrg;

    cConvsOrg = cConvs;
    cConvs = 0;
    if (hConvList) {
        /*
         * Enumerate all the conversations within this list - note that
         * DDEML will only return active conversations.  Inactive conversations
         * are automatically removed.
         */
        hConv = DdeQueryNextServer(hConvList, 0);
        while (hConv) {
            /*
             * Count the active conversations while we're at it.
             */
            cConvs++;
            /*
             * Spawn an asynchronous transaction - this was chosen because
             * we have not particular action if an error ocurrs so we just
             * don't care too much about the results - this technique will
             * NOT do for XTYP_REQUEST transactions though.
             */
            if (DdeClientTransaction(pSrc, cbData, hConv, hszAppName, fmt,
                    xtyp, TIMEOUT_ASYNC, &dwResult)) {
                /*
                 * We immediately abandon the transaction so we don't get
                 * a bothersome XTYP_XACT_COMPLETE callback which we don't
                 * care about.
                 */
                DdeAbandonTransaction(idInst, hConv, dwResult);
            }

            hConv = DdeQueryNextServer(hConvList, hConv);
        }
    }
    if (cConvs != cConvsOrg) {
        /*
         * Oh, the number of active conversations has changed.  Time to
         * repaint!
         */
        InvalidateRect(hwndMain, NULL, TRUE);
    }
}


/*
 * MyProcessKey
 *
 * We demonstrate the robustness of NT here by forcing a GP anytime the
 * 'B' key is pressed while this window has the focus.  NT should properly
 * fake termination to all other apps connected to us.
 */
VOID MyProcessKey(
TCHAR tchCode,
LONG lKeyData)
{
    switch (tchCode) {
    case TEXT('B'):
    case TEXT('b'):
        *((PBYTE)(-1)) = 0;    // Cause GP fault!
        break;
    }
}



LONG  APIENTRY MainWndProc(
HWND hwnd,
UINT message,
WPARAM wParam,
LONG lParam)
{
    RECT rc;

    switch (message) {
    case WM_CREATE:
        /*
         * initially we are inactive - this reduces some of the message
         * traffic while we are initializing - but we could start active fine.
         */
        fActive = FALSE;
        break;

    case WM_RBUTTONDOWN:
        if (GetKeyState(VK_CONTROL) & 0x8000) {
            /*
             * A CTRL R_BUTTON click will cause ALL instances of this app
             * to become inactive.
             */
            BroadcastTransaction((PBYTE)szPause, sizeof(szPause), 0, XTYP_EXECUTE);
            MessageBeep(0);
        }
        /*
         * A R_BUTTON click makes us inactive.  Repaint to show state change.
         * We do a synchronous update in case there is too much DDE message
         * activity to allow the WM_PAINT messages through.  Remember DDE
         * messages have priority over others!
         */
        KillTimer(hwndMain, 1);
        fActive = FALSE;
        InvalidateRect(hwnd, NULL, TRUE);
        UpdateWindow(hwnd);
        break;

    case WM_LBUTTONDOWN:
        if (GetKeyState(VK_CONTROL) & 0x8000) {
            /*
             * A CTRL L_BUTTON click will cause ALL instances of this app
             * to become active.
             */
            BroadcastTransaction((PBYTE)szResume, sizeof(szResume), 0, XTYP_EXECUTE);
            MessageBeep(0);
        }
        /*
         * An L_BUTTON click makes us active.  Repaint to show state change.
         */
        SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
        fActive = TRUE;
        InvalidateRect(hwnd, NULL, TRUE);
        UpdateWindow(hwnd);
        break;

    case WM_CHAR:
        MyProcessKey((TCHAR)wParam, lParam);
        break;

    case WM_TIMER:
        /*
         * We use timers for simplicity.  On Win3.1 we could run out of
         * timers easily but we don't have this worry on NT.
         *
         * Each tick, we increment our data and call DdePostAdvise() to
         * update any links there may be on this data.  DDEML makes link
         * updates on specific items quite easy.
         */
        count++;
        DdePostAdvise(idInst, hszAppName, hszAppName);
        /*
         * Invalidate the part of ourselves that shows our data and
         * synchronously update it in case DDE message activity is blocking
         * paints.
         */
        SetRect(&rc, 0, 0, cxText, cyText);
        InvalidateRect(hwndMain, &rc, TRUE);
        UpdateWindow(hwndMain);
        break;

    case WM_PAINT:
        PaintDemo(hwnd);
        break;

    case WM_CLOSE:
        KillTimer(hwnd, 1);
        /*
         * We do DDE cleanup here.  It is best to do DDE cleanup while
         * still in the message loop to allow DDEML to recieve messages
         * while shutting down.
         */
        DdeDisconnectList(hConvList);
        DdeNameService(idInst, 0, 0, DNS_UNREGISTER);
        DdeFreeStringHandle(idInst, hszAppName);
        DdeUninitialize(idInst);
        PostQuitMessage(0);
        break;

    default:
        return (DefWindowProc(hwnd, message, wParam, lParam));
    }
    return(0);
}


VOID PaintDemo(
HWND hwnd)
{
    PAINTSTRUCT ps;
    RECT rc;
    HCONV hConv;
    CONVINFO ci;
    int cConvsOrg = cConvs;

    BeginPaint(hwnd, &ps);
    /*
     * Draw our data on top - Black for active, Grey for inactive.
     */
    SetRect(&rc, 0, 0, cxText, cyText);
    SetBkMode(ps.hdc, TRANSPARENT);
    SetTextColor(ps.hdc, 0x00FFFFFF);   // white text
    FillRect(ps.hdc, &rc, GetStockObject(fActive ? BLACK_BRUSH : GRAY_BRUSH));
    DrawText(ps.hdc, ITOA(count, szT, 10), -1, &rc, DT_CENTER | DT_VCENTER);

    /*
     * Now draw the most recently recieved data from each server we are
     * connected to.
     */
    if (hConvList) {
        OffsetRect(&rc, 0, cyText);
        SetTextColor(ps.hdc, 0);    // draw black text
        cConvs = 0;
        hConv = DdeQueryNextServer(hConvList, 0);
        while (hConv) {
            cConvs++;
            /*
             * count how many conversations are active while we're at it.
             */
            ci.cb = sizeof(CONVINFO);
            DdeQueryConvInfo(hConv, QID_SYNC, &ci);
            FillRect(ps.hdc, &rc, GetStockObject(WHITE_BRUSH));  // white bkgnd
            DrawText(ps.hdc, ITOA(ci.hUser, szT, 10), -1, &rc,
                    DT_CENTER | DT_VCENTER);
            OffsetRect(&rc, 0, cyText);
            hConv = DdeQueryNextServer(hConvList, hConv);
        }
    }
    EndPaint(hwnd, &ps);
    if (cConvsOrg != cConvs) {
        /*
         * The number of active conversations changed!  Resize to fit.
         */
        SetWindowPos(hwndMain, 0, 0, 0, cxText,
                (cyText * (cConvs + 1)) + cyTitle,
                SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
    }
}



/*
 * This is the main DDEML callback proc.  It handles all interaction with
 * DDEML that is DDEML originated.
 */
HDDEDATA CALLBACK DdeCallback(
WORD wType,
WORD wFmt,
HCONV hConv,
HSZ hszTopic,
HSZ hszItem,
HDDEDATA hData,
DWORD lData1,
DWORD lData2)
{
    LPTSTR pszExec;

    switch (wType) {
    case XTYP_CONNECT:
        /*
         * Only allow connections to us.  We can always return TRUE because
         * the CBF_FILTERINITS bit given to DdeInitialize() told DDEML to
         * never bother us with connections to any service names other than
         * what we have registered.
         *
         * Note that we do not handle the XTYP_WILD_CONNECT transaction.
         * This means that no wild-card initiates to us will work.
         */
        return((HDDEDATA)TRUE);

    case XTYP_ADVREQ:
    case XTYP_REQUEST:
        /*
         * These two transactions are the only ones that require us to
         * render our data.  By using a custom format, we don't have to
         * convert our count to text form to support CF_TEXT.
         */
        return(DdeCreateDataHandle(idInst, (PBYTE)&count, sizeof(count), 0,
                hszAppName, OurFormat, 0));

    case XTYP_ADVSTART:
        /*
         * Only allow links to our Item in our format.
         */
        return(HDDEDATA) ((UINT)wFmt == OurFormat && hszItem == hszAppName);

    case XTYP_ADVDATA:
        /*
         * Data is comming in.  We don't bother with XTYP_POKE transactions,
         * but if we did, they would go here.  Since we only allow links
         * on our item and our format, we need not check these here.
         */
        if (DdeGetData(hData, (PBYTE)&InCount, sizeof(InCount), 0)) {
            DdeSetUserHandle(hConv, QID_SYNC, InCount);
        }
        /*
         * update ourselves to reflect the new incomming data.
         */
        InvalidateRect(hwndMain, NULL, TRUE);
        /*
         * This transaction requires a flag return value.  We could also
         * stick other status bits here if needed but its not recommended.
         */
        return((HDDEDATA)DDE_FACK);

    case XTYP_EXECUTE:
        /*
         * Another instance wants us to do something.  DdeAccessData()
         * makes parsing of execute strings easy.  Also note, that DDEML
         * will automatically give us the string in the right form
         * (UNICODE vs ASCII) depending on which form of DdeInitialize()
         * we called.
         */
        pszExec = (LPTSTR)DdeAccessData(hData, NULL);
        if (pszExec) {
            if (fActive && !STRICMP(szPause, pszExec)) {
                KillTimer(hwndMain, 1);
                fActive = FALSE;
                InvalidateRect(hwndMain, NULL, TRUE);
                UpdateWindow(hwndMain);
            } else if (!fActive && !STRICMP(szResume, pszExec)) {
                SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
                fActive = TRUE;
                InvalidateRect(hwndMain, NULL, TRUE);
                UpdateWindow(hwndMain);
            }
            /*
             * The beep gives good feedback on how fast the execute was.
             */
            MessageBeep(0);
        }
        break;

    case XTYP_DISCONNECT:
        /*
         * Somebody went away, repaint so we update our cConvs count.
         */
        InvalidateRect(hwndMain, NULL, TRUE);
        break;

    case XTYP_REGISTER:
        /*
         * Since a new server just arrived, lets make sure our links are
         * up to date.  Note that only one link on a
         * conversation/topic/item/format set will work anyway so we don't
         * worry about duplicate links.
         *
         * Note also that we are using hszItem - which is the InstanceSpecific
         * name of the server that is registering.  This greatly reduces the
         * number of messages that go flying around.
         */
		BroadcastTransaction( NULL, 0, OurFormat, XTYP_ADVSTOP );
        hConvList = DdeConnectList(idInst, hszItem, hszAppName, hConvList, NULL);
        BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART);
        SetWindowPos(hwndMain, 0, 0, 0, cxText,
                (cyText * (cConvs + 1)) + cyTitle, SWP_NOMOVE | SWP_NOZORDER);
        UpdateWindow(hwndMain);
        return((HDDEDATA)TRUE);
    }
    return(0);
}
